<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <title>ウノウラボ Unoh Labs</title>
    <link rel="alternate" type="text/html" href="http://labs.unoh.net/" />
    <link rel="self" type="application/atom+xml" href="http://labs.unoh.net/atom.xml" />
    <id>tag:labs.unoh.net,2006://2</id>
    <link rel="service.post" type="application/atom+xml" href="http://www.unoh.net/mt32/mt-atom.cgi/weblog/blog_id=2" title="ウノウラボ Unoh Labs" />
    <updated>2009-06-29T08:40:40Z</updated>
    <subtitle>ウノウ株式会社のデモ版サービスやTIPSなどの情報</subtitle>
    <generator uri="http://www.sixapart.com/movabletype/">Movable Type  4.261</generator>
 

<entry>
    <title>10テラバイトマシンのつくりかた</title>
    <link rel="alternate" type="text/html" href="http://labs.unoh.net/2009/06/10_2.html" />
    <link rel="service.edit" type="application/atom+xml" href="http://www.unoh.net/mt32/mt-atom.cgi/weblog/blog_id=2/entry_id=1655" title="10テラバイトマシンのつくりかた" />
    <id>tag:labs.unoh.net,2009://2.1655</id>
    
    <published>2009-06-26T06:26:48Z</published>
    <updated>2009-06-29T08:40:40Z</updated>
    
    <summary> 「iPodの残り容量が200MBを切った」と社内で発言してから「iPhoneを買おう！」としきりに言われるようになったbokkoです。そんな私は先月、ホコリをかぶっていたデスクトップPCを筐体ごと買い換えました。今ではMacBookからSSHでログインしてターミナル上で快適な生活を送っています。 今月、2TBのHDDを6本使ったサーバを立てる機会がありまして、今日はその時のお話です。...</summary>
    <author>
        <name>unoh</name>
        
    </author>
    
        <category term="Linux" />
    
    <content type="html" xml:lang="ja" xml:base="http://labs.unoh.net/">
        
「iPodの残り容量が200MBを切った」と社内で発言してから「iPhoneを買おう！」としきりに言われるようになったbokkoです。そんな私は先月、ホコリをかぶっていたデスクトップPCを筐体ごと買い換えました。今ではMacBookからSSHでログインしてターミナル上で快適な生活を送っています。

今月、2TBのHDDを6本使ったサーバを立てる機会がありまして、今日はその時のお話です。
        <![CDATA[
<h3>HDDの容量とストレージサーバ</h3>

Webサービスのインフラを構築・運営していると、膨大なデータをどう扱うかといった問題にぶち当たることがあります。仮想化技術の進歩によって複数のOSを1台のマシンで同時に稼働させつつ、物理的なマシンの数を減らすことができるようにはなりましたが、

物理的な媒体であるHDDを1台のマシンに搭載できる数には限りがあり、ソフトウェアであるOSの仮想化みたいにじゃんじゃん増やすことができません。

なので、できる限り容量の大きいディスクを使いたいところです。しかし、RAIDを構築して冗長化したりするとかになるとディスクが余分に必要になるので、マシン1台あたりの最大容量が大幅に減ることになります。

普通のマシンのマザーボードだとSATAの口が数個しかありませんから、かなり厳しくなります。こういう場合、高価ですが、信頼性が高く、たくさんのディスクを扱うことのできるストレージ製品を使うという選択肢があります。この手の製品には大量のHDDを十数本並べてもパーティションがあたかも1個しかないように見えたりするものがあります。

パーティションが1個がしかないことによる利点はそのパーティションに割り当てられた容量を越えない限り、データは単純にそのパーティション内に放り込んでいくだけで、データを参照する際にデータはどこにあるのかなんてほとんど気にしなくて済むことです。

さらに、通常のマシンと違って、マザーボードにSATAの口が4～6本しかないなんてこともなく、大量のディスクを使うことができるので、RAID5やRAID6(およびホットスペア)のようにデータの保存とは別にディスクを大量に使う冗長化構成でも十分なディスク領域が確保できます。

ただ、当然ながらこの選択は初期の金銭的なコストが高くなります。これ以外の方法だと、例えば(テラバイト級の)膨大なデータを扱う場合にはストレージサーバをたくさん並べて、どのファイルがどのサーバのどの場所にあるのかといったメタデータを保存しておいて、

データを参照する際はそのメタデータを元にデータのありかを探すようにし、「実際にはストレージはたくさんあるんだけど、1個しかないように見せる」所謂、分散ストレージ的なシステムを構築するという方法があります。

こちらは初期の金銭的なコストは安くなりますが、さきほど述べた仕組みを(何らかのミドルウェア、例えばMogileFS等を使うにしろおそらくは)自前で構築することになるので、事前にちゃんと設計しないとかえって高くつくことになります。


で、フォト蔵はというと現在はPHPやMySQL、自作のApacheモジュールを駆使して、データを複数のサーバ上のディスクに分散させて読み書きする仕組みを構築しています。
最近はようやくストレージサーバの追加も比較的楽にできるようになりました。あと、新しい仕組みを構築する上で久しぶりに仕事でC言語を使いました。


ただ、時と場合によっては非常に大きなディスク領域を1つのパーティションとして扱いたい場面もあるかと思います。でも、普通のマシンでできるだけ簡単にやりたい。そういう時のためにLVMがあります。


前置きが長くなりました。では、LVMと2TBのHDD6本を使って10テラバイトなマシンを作ったときの様子を紹介します。ただし、写真はありません。

<h3>LVM</h3>

LVMはLogic Volume Managnerの略で、複数のHDDを1つの論理的なディスク領域(パーティション)として扱ったり、グループ化することができるディスク管理ユーティリティの総称です。
LVMを使うと複数のHDDを使って巨大なディスク領域を持つ1つのパーティションを作成したり、ディスク容量が足りなくなっても新しいHDDを追加していくつかのユーティリティコマンドを走らせるだけでディスク領域を拡張することができます。
最近のLinuxディストリビューションにはAnacondaをはじめとする便利なインストーラやGUIアプリケーションがあり、インストール時にパーティションの作成からフォーマットはもちろん、LVMの設定もグラフィカルに行うことができますが、ここでは敢えてコマンドラインでやります。

<h3>LVMの構成要素</h3>

LVMを使う前にLVMの重要な3つの構成要素について軽く紹介します。

<ul>
<li>Phisical Volume(以下、PV)</li>
<li>Volume Group(以下、VG)</li>
<li>Logical Volume(以下、LV)</li>
</ul>

PVは物理的なディスク領域(パーティション)を指し、この物理的なディスク領域をまとめたものがVG(ボリュームグループ)です。また、LVはこのVG内に作成され、実際にユーザが使う論理的なディスク領域(パーティション)になります。このへんは言葉で説明するよりも実際にやってみた方がわかりやすいので、とりえあえず、実際にLVMを使ってみましょう。

<h3>LVMパーティションを作成する</h3>

ディスクに対してLVMのユーティリティを実行するには、事前に各ディスク上にLVMパーティションを作成する必要があります。なので、まずfdiskを使って各ディスク上にLVMパーティションを作成します。各ディスクのパーティションは以下の通りです。

<pre class="code">
# fdisk -l /dev/sda
Disk /dev/sda: 2000.3 GB, 2000398934016 bytes
255 heads, 63 sectors/track, 243201 cylinders
Units = cylinders of 16065 * 512 = 8225280 bytes
 
   Device Boot      Start         End      Blocks   Id  System
/dev/sda1   *           1          13      104391   83  Linux
/dev/sda2              14        6387    51199155   83  Linux
/dev/sda3            6388        6518     1052257+  82  Linux swap / Solaris
# fdisk -l /dev/sdb
Disk /dev/sdb: 2000.3 GB, 2000398934016 bytes
255 heads, 63 sectors/track, 243201 cylinders
Units = cylinders of 16065 * 512 = 8225280 bytes
 
   Device Boot      Start         End      Blocks   Id  System
# fdisk -l /dev/sdc
Disk /dev/sdc: 2000.3 GB, 2000398934016 bytes
255 heads, 63 sectors/track, 243201 cylinders
Units = cylinders of 16065 * 512 = 8225280 bytes
 
   Device Boot      Start         End      Blocks   Id  System
# fdisk -l /dev/sdd
Disk /dev/sdd: 2000.3 GB, 2000398934016 bytes
255 heads, 63 sectors/track, 243201 cylinders
Units = cylinders of 16065 * 512 = 8225280 bytes
 
   Device Boot      Start         End      Blocks   Id  System
# fdisk -l /dev/sde
Disk /dev/sde: 2000.3 GB, 2000398934016 bytes
255 heads, 63 sectors/track, 243201 cylinders
Units = cylinders of 16065 * 512 = 8225280 bytes
 
   Device Boot      Start         End      Blocks   Id  System
# fdisk -l /dev/sdf
Disk /dev/sdf: 2000.3 GB, 2000398934016 bytes
255 heads, 63 sectors/track, 243201 cylinders
Units = cylinders of 16065 * 512 = 8225280 bytes
 
   Device Boot      Start         End      Blocks   Id  System
#
</pre>

/dev/sdaにはブートやルート、およびスワップパーティションが存在しますが、残りのディスクは基本的に同じです。まず、以下のように/dev/sda上にLVMパーティションを作成します。

<pre class="code">
# fdisk /dev/sda
The number of cylinders for this disk is set to 243201.
There is nothing wrong with that, but this is larger than 1024,
and could in certain setups cause problems with:
1) software that runs at boot time (e.g., old versions of LILO)
2) booting and partitioning software from other OSs
   (e.g., DOS FDISK, OS/2 FDISK)
 
Command (m for help): n
Command action
  e   extended
  p   primary partition (1-4)
p
Selected partition 4
First cylinder (6519-243201, default 6519):
Using default value 6519
Last cylinder or +size or +sizeM or +sizeK (6519-243201, default 243201):
Using default value 243201
 
Command (m for help): t
Partition number (1-4): 4
Hex code (type L to list codes): 8e
Changed system type of partition 4 to 8e (Linux LVM)
 
Command (m for help): p
 
Disk /dev/sda: 2000.3 GB, 2000398934016 bytes
255 heads, 63 sectors/track, 243201 cylinders
Units = cylinders of 16065 * 512 = 8225280 bytes
 
 
   Device Boot      Start         End      Blocks   Id  System
/dev/sda1   *           1          13      104391   83  Linux
/dev/sda2              14        6387    51199155   83  Linux
/dev/sda3            6388        6518     1052257+  82  Linux swap / Solaris
/dev/sda4            6519      243201  1901156197+  8e  Linux LVM
 
Command (m for help): w
The partition table has been altered!
 
Calling ioctl() to re-read partition table.
 
WARNING: Re-reading the partition table failed with error 16: Device or resource busy.
The kernel still uses the old table.
The new table will be used at the next reboot.
Syncing disks
#
</pre>

続いて/dev/sdb上にLVMパーティションを作成します。

<pre class="code">
# fdisk /dev/sdb
The number of cylinders for this disk is set to 243201.
There is nothing wrong with that, but this is larger than 1024,
and could in certain setups cause problems with:
1) software that runs at boot time (e.g., old versions of LILO)
2) booting and partitioning software from other OSs
   (e.g., DOS FDISK, OS/2 FDISK)
 
Command (m for help): n
Command action
   e   extended
   p   primary partition (1-4)
p
Partition number (1-4): 1
First cylinder (1-243201, default 1):
Using default value 1
Last cylinder or +size or +sizeM or +sizeK (1-243201, default 243201):
Using default value 243201
 
Command (m for help): p
 
Disk /dev/sdb: 2000.3 GB, 2000398934016 bytes
255 heads, 63 sectors/track, 243201 cylinders
Units = cylinders of 16065 * 512 = 8225280 bytes
 
    Device Boot      Start         End      Blocks   Id  System
/dev/sdb1               1      243201  1953512001   83  Linux
 
Command (m for help): t
Selected partition 1
Hex code (type L to list codes): 8e
Changed system type of partition 1 to 8e (Linux LVM)
 
Command (m for help): p
 
Disk /dev/sdb: 2000.3 GB, 2000398934016 bytes
255 heads, 63 sectors/track, 243201 cylinders
Units = cylinders of 16065 * 512 = 8225280 bytes
 
   Device Boot      Start         End      Blocks   Id  System
/dev/sdb1               1      243201  1953512001   8e  Linux LVM
 
Command (m for help): w
The partition table has been altered!
 
Calling ioctl() to re-read partition table.
Syncing disks.
#
</pre>

ほかのディスクでも同じようにLVMパーティションを作成していきます。

<pre class="code">
# fdisk /dev/sdc
(略)
# fdisk /dev/sdd
(略)
# fdisk /dev/sde
(略)
# fdisk /dev/sdf
(略)
#
</pre>

全部のデバイスでLVMパーティションを作成し終わったら実行結果を反映させるため、再起動します。ちなみにfdiskは2TB以下のディスク領域しか扱うことができないので、将来に備えてpartedのことを勉強しておくのもいいかもしれません。

<h3>PVを作成する</h3>

再起動したら次は各ディスクのLVMパーティションを使ってPVを作成します。まずは/dev/sda4から。

<pre class="code">
# pvcreate /dev/sda4
  Physical volume "/dev/sda4" successfully created
#
</pre>

作成したPVの一覧を確認するにはpvdisplayコマンドを使います。

<pre class="code">
# pvdisplay
"/dev/sda4" is a new physical volume of "1.77 TB"
  --- NEW Physical volume ---
  PV Name               /dev/sda4
  VG Name
  PV Size               1.77 TB
  Allocatable           NO
  PE Size (KByte)       0
  Total PE              0
  Free PE               0
  Allocated PE          0
  PV UUID               7zZFwx-ccj9-CONj-F1fi-YD2Q-hdBT-m0rU5p
 
#					       
</pre>

指定した/dev/sda4がPVとして登録されているのが確認できます。ほかのLVMパーティションに対しても同じようにPVを作成していきます。

<pre class="code">
# pvcreate /dev/sdb1
  Physical volume "/dev/sdb1" successfully created
# pvcreate /dev/sdc1
  Physical volume "/dev/sdc1" successfully created
# pvcreate /dev/sdd1
  Physical volume "/dev/sdd1" successfully created
# pvcreate /dev/sde1
  Physical volume "/dev/sde1" successfully created
# pvcreate /dev/sdf1
  Physical volume "/dev/sdf1" successfully created
# 
</pre>
  
<h3>VGを作成する</h3>

次に、VolGroup00というVGを作成してそのVGに/dev/sda4のPVを追加します。

<pre class="code">
# vgcreate VolGroup00 /dev/sda4
  Volume group "VolGroup00" successfully created
#
</pre>

VGの情報はvgdisplayで確認することができます。

<pre class="code">
# vgdisplay
  --- Volume group ---
  VG Name               VolGroup00
  System ID
  Format                lvm2
  Metadata Areas        1
  Metadata Sequence No  1
  VG Access             read/write
  VG Status             resizable
  MAX LV                0
  Cur LV                0
  Open LV               0
  Max PV                0
  Cur PV                1
  Act PV                1
  VG Size               1.77 TB
  PE Size               32.00 MB
  Total PE              464149
  Alloc PE / Size       0 / 0
  Free  PE / Size       464149 / 1.77 TB
  VG UUID               tSSUzh-8aQQ-aZd7-SivA-dBiz-dQZp-tzgzvd
 
#					  
</pre>

<h3>VGを拡張する</h3>

さきほど作成したVGにほかのPVを追加して容量を増やしてみます。

<pre class="code">
# vgextend VolGroup00 /dev/sdb1
  Volume group "VolGroup00" successfully extended
# 
</pre>

追加できたかどうかvgdisplayで確認します。(VG Sizeに注目)

<pre class="code">
# vgdisplay
  --- Volume group ---
  VG Name               VolGroup00
  System ID
  Format                lvm2
  Metadata Areas        2
  Metadata Sequence No  2
  VG Access             read/write
  VG Status             resizable
  MAX LV                0
  Cur LV                0
  Open LV               0
  Max PV                0
  Cur PV                2
  Act PV                2
  VG Size               3.59 TB
  PE Size               32.00 MB
  Total PE              941080
  Alloc PE / Size       0 / 0
  Free  PE / Size       941080 / 3.59 TB
  VG UUID               tSSUzh-8aQQ-aZd7-SivA-dBiz-dQZp-tzgzvd
# 
</pre>

無事、追加できたようです。同じ要領で残りのディスクも追加していきます。

<pre class="code">
# vgextend VolGroup00 /dev/sdc1
  Volume group "VolGroup00" successfully extended
# vgextend VolGroup00 /dev/sdd1
  Volume group "VolGroup00" successfully extended
# vgextend VolGroup00 /dev/sde1
  Volume group "VolGroup00" successfully extended
# vgextend VolGroup00 /dev/sdf1
  Volume group "VolGroup00" successfully extended
#
</pre>

もう一度lvdisplayで状態を確認してみましょう。

<pre class="code">
# vgdisplay
  --- Volume group ---
  VG Name               VolGroup00
  System ID
  Format                lvm2
  Metadata Areas        6
  Metadata Sequence No  13
  VG Access             read/write
  VG Status             resizable
  MAX LV                0
  Cur LV                1
  Open LV               1
  Max PV                0
  Cur PV                6
  Act PV                6
  VG Size               10.87 TB
  PE Size               32.00 MB
  Total PE              356098
  Alloc PE / Size       354878 / 10.83 TB
  Free  PE / Size       1220 / 38.12 GB
  VG UUID               KctZmn-mBM5-ZfNn-LyBH-iT0B-E8fW-Yn2rp1
#
</pre>

これで10.87TBのVGができました。

<h3>LVを作成する</h3>

VGが作成できたら、その作成したVG内に実際に扱うことのできる論理的なディスク領域、LVを作成します。

<pre class="code">
# lvcreate -L10.8T -n lvol0 VolGroup00 # VolGroup00内にlvol0という名前で容量が10.8TBのLVを作成する
  Rounding up size to full physical extent 10.80 TB
  Logical volume "lvol0" created
#
</pre>

作成できたかどうかはlvdisplayで確認します。

<pre class="code">
# lvdisplay
  --- Logical volume ---
  LV Name                /dev/VolGroup00/lvol0
  VG Name                VolGroup00
  LV UUID                YY3Mah-Yk2h-Sy06-9FVs-9bLx-vFp8-AyWheH
  LV Write Access        read/write
  LV Status              available
  # open                 0
  LV Size                10.80 TB
  Current LE             353895
  Segments               6
  Allocation             inherit
  Read ahead sectors     auto
  - currently set to     256
  Block device           253:0
#			      
</pre>

なお、初期のLVMではPE(物理エクステント)の最大数やカーネルの実装による制限で、LVの最大容量が(具体的な数字は忘れましたが、確か1TBか2TBぐらいに)制限されていましたが、
現在のLVM2はこの制限が大幅に緩和されています。(ただし、それでもファイルシステム等による制限は付きまといます)

また↑の方法とこのマシン<b>だけ</b>だと冗長性が確保できないので、実際には何かしらの方法(例えばDRBD)で冗長化する必要があるのでしょう。

<h3>仕上げ</h3>

あとはこのLVをmkfs系のコマンドでフォーマットすれば大容量マシンの出来上がりです。]]>
    </content>
</entry>

<entry>
    <title>RDBで階層構造を扱うには？</title>
    <link rel="alternate" type="text/html" href="http://labs.unoh.net/2009/06/rdb.html" />
    <link rel="service.edit" type="application/atom+xml" href="http://www.unoh.net/mt32/mt-atom.cgi/weblog/blog_id=2/entry_id=1653" title="RDBで階層構造を扱うには？" />
    <id>tag:labs.unoh.net,2009://2.1653</id>
    
    <published>2009-06-24T08:23:54Z</published>
    <updated>2009-06-24T15:04:58Z</updated>
    
    <summary>yukiです。ダイエットを始めて3kg減ったと思ったら、風邪を引いて見事に1kg増量。運動しないと駄目ですね。あと残り20kg、道のりは遠いです。 さて今回は、「RDBで階層構造を扱うには？」です。あるサイトを構築中に階層構造をもったカテゴリ構造にすることになり、DBでどのように扱うか悩みました。 DBはMySQLを採用していたので、この時点でぱっと頭に浮かんだ選択肢は以下のようなものでした。 XML-DBを利用する 親カテゴリレコードのプライマリIDを子カテゴリレコードに持たせる 親を含めた『絶対パス』を名称として扱い、取り出した後にパース ファイルシステムに同様のディレクトリ構造を作り、毎回パースする (1)のXMLDBはオープンソースのeXistやXindice、Yggdrasillなど様々な選択肢がありましたが、カテゴリのみの利用な割にメンテナンスコストが高すぎるので見送りました。...</summary>
    <author>
        <name>unoh</name>
        
    </author>
    
    <content type="html" xml:lang="ja" xml:base="http://labs.unoh.net/">
        <![CDATA[<p>yukiです。ダイエットを始めて3kg減ったと思ったら、風邪を引いて見事に1kg増量。<br />運動しないと駄目ですね。あと残り20kg、道のりは遠いです。</p>

<p>さて今回は、「RDBで階層構造を扱うには？」です。<br />あるサイトを構築中に階層構造をもったカテゴリ構造にすることになり、DBでどのように扱うか悩みました。<br />
DBはMySQLを採用していたので、この時点でぱっと頭に浮かんだ選択肢は以下のようなものでした。</p>
<ol>
<li>XML-DBを利用する</li>
<li>親カテゴリレコードのプライマリIDを子カテゴリレコードに持たせる</li>
<li>親を含めた『絶対パス』を名称として扱い、取り出した後にパース</li>
<li>ファイルシステムに同様のディレクトリ構造を作り、毎回パースする</li>
</ol>
<p>(1)のXMLDBはオープンソースのeXistやXindice、Yggdrasillなど様々な選択肢がありましたが、カテゴリのみの利用な割にメンテナンスコストが高すぎるので見送りました。<br />
(2)の親ID格納の実装は容易ですが、取り出し時のクエリが何回も行われるなど、負荷がかかるうえ、何よりエレガントさがありません。<br />
(3)は一旦採用しかけたのですが、取り出しロジックの複雑さが増すので、できればやりたくありません。<br />
(4)は柔軟な対応や検索など相当大変な気がしたので見送り。<br />
すべての案がボツになったところで、いいアイデアはないかとグーグル先生に聞いてみたところ、以下のページが目からウロコだったのでさっそく採用してみました。</p>

<p>Storing Hierarchical Data in a Database<br />
<a href="http://www.sitepoint.com/article/hierarchical-data-database/">http://www.sitepoint.com/article/hierarchical-data-database/</a></p>

<p>英語ですが、PHPとMySQLのソースも掲載されているので、それほど難しくないと思います。<br />
おおざっぱに説明すると、ルートを頂点として各ノードの左右に順にIDを振り、このIDを利用して範囲検索することで、柔軟な取得を可能にするというものでした。<br />
例となる構造をここに示します。<p>

<pre class="code"><code>                     +--------+
                    1|ジョージ|20
                     +--------+
                          |
                  +-------+-----------+
                  |                   |
              +----------+        +------+
             2|ジョナサン|15    16|ディオ|19
              +----------+        +------+
                  |                   |
             +-----------+       +--------+
            3|ジョージ2世|14   17|ジョルノ|18
             +-----------+       +--------+
                  |
              +--------+
             4|ジョセフ|13
              +--------+
                  |
           +------+-------+
           |              |
       +------+      +--------+
      5|ホリィ|10  11|東方仗助|12
       +------+      +--------+
           |
     +----------+
    6|空条承太郎|9
     +----------+
           |
      +--------+
     7|空条徐倫|8
      +--------+
</code></pre>

<p>ルートにあたるジョージの左IDが1から始まり、左から順に子孫に番号を振りつつ、
末端の空条徐倫までたどり着くと右IDを振りつつ戻ります。<br />
同様に、兄弟のディオの系列にも番号を振り、最終的にジョージへ戻ります。<br />
テーブルにすると以下のようになります。</p>

<pre class="code"><code>CREATE TABLE family (
  "id" INTEGER NOT NULL AUTO_INCREMENT,
  "left_id" INTEGER NOT NULL,
  "right_id" INTEGER NOT NULL,
  "name" VARCHAR(20) NOT NULL,
  "gender" CAHR(1) DEFAULT "M" NOT NULL,
  "stand" VARCHAR(100)
  PRIMARY KEY ("id")
);

+--+-------+--------+-----------+------+--------------------------+
|id|left_id|right_id|   name    |gender|           stand          |
+--+-------+--------+-----------+------+--------------------------+
| 1|      1|      20|ジョージ   |  M   |                          |
| 2|      2|      15|ジョナサン |  M   |                          |
| 3|      3|      14|ジョージ2世|  M   |                          |
| 4|      4|      13|ジョセフ   |  M   |ハーミット・パープル      |
| 5|      5|      10|ホリィ     |  F   |                          |
| 6|      6|       9|空条承太郎 |  M   |スター・プラチナ          |
| 7|      7|       8|空条徐倫   |  F   |ストーン・フリー          |
| 8|     11|      12|東方仗助   |  M   |クレイジー・ダイヤモンド  |
| 9|     16|      19|ディオ     |  M   |ザ・ワールド              |
|10|     17|      18|ジョルノ   |  M   |ゴールド・エクスペリエンス|
+--+-------+--------+-----------+------+--------------------------+
</code></pre>

<p>親を含めて子孫を取得する場合、左右IDの数字を指定してやれば取得できます。<br />
例としてジョセフとその子孫を取得する場合は以下のようになります。</p>

<pre class="code"><code> SELECT left_id, right_id, name
   FROM family
  WHERE left_id BETWEEN 4 AND 13
  ORDER BY left_id ASC

+-------+--------+-----------+
|left_id|right_id|   name    |
+-------+--------+-----------+
|      4|      13|ジョセフ   |
|      5|      10|ホリィ     |
|      6|       9|空条承太郎 |
|      7|       8|空条徐倫   |
|     11|      12|東方仗助   |
+-------+--------+-----------+
</code></pre>

<p>子孫から親をたどる場合、左IDが子ノードの左ID以下、右IDが子ノードの右以上のIDを探します。<br />
例として東方仗助の親を探す場合は以下のようになります。</p>

<pre class="code"><code> SELECT left_id, right_id, name
   FROM family
  WHERE left_id  < 11
    AND right_id > 12
  ORDER BY left_id ASC

+-------+--------+-----------+
|left_id|right_id|   name    |
+-------+--------+-----------+
|      1|      20|ジョージ   |
|      2|      15|ジョナサン |
|      3|      14|ジョージ2世|
|      4|      13|ジョセフ   |
|     11|      12|東方仗助   |
+-------+--------+-----------+
</code></pre>

<p>あるノードから数えた子孫の数は、以下の計算式で算出します。</p>

<pre class="code"><code>(ノード右ID - ノード左ID - 1) / 2</code></pre>

<p>ジョナサンを例にすると、</p>

<pre class="code"><code>(15 - 2 - 1) / 2 = 6</code></pre>

<p>となり、子孫は6人となります。</p>

<p>また、新たにノードを増やす場合、あらかじめ左右のIDをずらしておきます。<br />
たとえば空条徐倫に娘ができた場合は以下のようになります。</p>

<pre class="code"><code> UPDATE family SET right_id = right_id + 2 WHERE right_id > 7 ORDER BY right_id DESC;
 UPDATE family SET left_id  = left_id  + 2 WHERE left_id  > 7 ORDER BY left_id DESC;
 INSERT INTO family (left_id, right_id, name, gender) VALUES(8, 9, '徐倫の娘');</code></pre>

<p>これを実行すると以下のようなデータになります。</p>

<pre class="code"><code>
+--+-------+--------+-----------+------+--------------------------+
|id|left_id|right_id|   name    |gender|           stand          |
+--+-------+--------+-----------+------+--------------------------+
| 1|      1|      22|ジョージ   |  M   |                          |
| 2|      2|      17|ジョナサン |  M   |                          |
| 3|      3|      16|ジョージ2世|  M   |                          |
| 4|      4|      15|ジョセフ   |  M   |ハーミット・パープル      |
| 5|      5|      12|ホリィ     |  F   |                          |
| 6|      6|      11|空条承太郎 |  M   |スター・プラチナ          |
| 7|      7|      10|空条徐倫   |  F   |ストーン・フリー          |
| 8|     13|      14|東方仗助   |  M   |クレイジー・ダイヤモンド  |
| 9|     18|      21|ディオ     |  M   |ザ・ワールド              |
|10|     19|      20|ジョルノ   |  M   |ゴールド・エクスペリエンス|
|11|      8|       9|徐倫の娘   |  F   |                          |
+--+-------+--------+-----------+------+--------------------------+

-- 徐倫の娘の親をたどる
 SELECT left_id, right_id, name
   FROM family
  WHERE left_id  < 8
    AND right_id > 9
  ORDER BY left_id ASC
+-------+--------+-----------+
|left_id|right_id|   name    |
+-------+--------+-----------+
|      1|      22|ジョージ   |
|      2|      17|ジョナサン |
|      3|      16|ジョージ2世|
|      4|      15|ジョセフ   |
|      5|      12|ホリィ     |
|      6|      11|空条承太郎 |
|      7|      10|空条徐倫   |
|      8|       9|徐倫の娘   |
+-------+--------+-----------+
</code></pre>

<p>この更新方法にはパフォーマンス上の様々な意見があるので、
このモデルをさらに推し進めた方法も存在するようです。<br />
今回はleft_idとright_idに整数値を利用しましたが、ノード追加時に更新が発生するのを防ぐため小数値を利用する方法もあります。<br />
具体的には左IDを8,右IDを9ではなく、左IDを7.3、右IDを7.6にすることで、他レコードへの更新を防ぎINSERT文1度で済むようにする、という方法です。<br />
この方法であれば、親子関係は維持したまま、だいぶパフォーマンスでは有利になります。<br />
私の場合はそれほど頻繁に更新しないのと、小数だと全体の見渡しがよくなくなると考えたため、この方法は採用しませんでした。</p>

<p>私は上記サイトでこの設計を知ったのですが、後になってWEB+DB PRESSを見直した所
同様の記事を見つました。(Vol49, 50の『SQLアタマアカデミー』著：ミック)
英語が苦手な方は是非こちらをお勧めします。<br />
また筆者の方のWEBサイトにはより詳細に網羅的に記載があります。</p>

<p>SQLで木と階層構造のデータを扱う（１）―― 入れ子集合モデル<br />
<a href="http://www.geocities.jp/mickindex/database/db_tree_ns.html">http://www.geocities.jp/mickindex/database/db_tree_ns.html</a><p>

<p>概念自体は古くからあるようなのでご存知の方は多いと思いますが、
今回の自分のように目からウロコがボロボロ落ちる方がいるかもしれないと思い
エントリーを書き起こしました。<br />よいSQLライフを！</p>

<p>[23:52] 「徐倫の娘」(ノード追加) についての実行結果と、小数を利用した方法を追記しました。</p>

<iframe src="http://rcm-jp.amazon.co.jp/e/cm?t=unoh-22&o=9&p=8&l=as1&asins=4774137529&fc1=000000&IS2=1&lt1=_blank&m=amazon&lc1=0000FF&bc1=000000&bg1=FFFFFF&f=ifr" style="width:120px;height:240px;" scrolling="no" marginwidth="0" marginheight="0" frameborder="0"></iframe>
]]>
        
    </content>
</entry>

<entry>
    <title>Operaのウィジェットを作ってみた</title>
    <link rel="alternate" type="text/html" href="http://labs.unoh.net/2009/06/opera-widget.html" />
    <link rel="service.edit" type="application/atom+xml" href="http://www.unoh.net/mt32/mt-atom.cgi/weblog/blog_id=2/entry_id=1650" title="Operaのウィジェットを作ってみた" />
    <id>tag:labs.unoh.net,2009://2.1650</id>
    
    <published>2009-06-17T11:26:29Z</published>
    <updated>2009-06-17T11:31:56Z</updated>
    
    <summary>yamaokaです。 Opera Uniteが話題になっていますね。 Operaを使うことが多いので、こうして話題になると少しだけうれしいです。 Operaは単なるwebブラウザーではありません。 Opera Uniteはともかく、メーラーにも、IRCのクライアントにも、 BitTorrentのクライアントにもなることができます。 そして、ウィジェットが動くプラットフォームでもあるのです。 Operaのウィジェットにはいろいろな種類があって、 例えばTwitterクライアントのTwipperaなど便利なものから、 ちょっとしたツールやゲームまで揃っています。 ウィジェットの開発はHTMLとJavaScript、CSSを使って行います。普通のwebサイト制作と同じですね。 開発者向けのドキュメントはまだ整備段階の感じもしますが、一通りの情報は揃っています。 というわけで（？）、試しにフォト蔵...</summary>
    <author>
        <name>unoh</name>
        
    </author>
    
    <content type="html" xml:lang="ja" xml:base="http://labs.unoh.net/">
        <![CDATA[<p>yamaokaです。</p>

<p>
<a href="http://unite.opera.com/">Opera Unite</a>が話題になっていますね。
Operaを使うことが多いので、こうして話題になると少しだけうれしいです。
Operaは単なるwebブラウザーではありません。
Opera Uniteはともかく、メーラーにも、IRCのクライアントにも、
BitTorrentのクライアントにもなることができます。
そして、ウィジェットが動くプラットフォームでもあるのです。
</p>

<p>
<a href="http://widgets.opera.com/">Operaのウィジェット</a>にはいろいろな種類があって、
例えばTwitterクライアントの<a href="http://widgets.opera.com/widget/6522/">Twippera</a>など便利なものから、
ちょっとしたツールやゲームまで揃っています。
ウィジェットの開発はHTMLとJavaScript、CSSを使って行います。普通のwebサイト制作と同じですね。
<a href="http://dev.opera.com/articles/widgets/">開発者向けのドキュメント</a>はまだ整備段階の感じもしますが、一通りの情報は揃っています。
</p>

<p>
というわけで（？）、試しに<a href="http://photozou.jp/">フォト蔵</a>の写真をスライドショー表示するウィジェットを作ってみました。
ウィジェットは次のファイルで構成されている必要があります。
</p>

<ul>
  <li>config.xml</li>
  <li>index.html</li>
</ul>

<p>
もちろん、画像やJavaScriptをディレクトリに分けておくことも可能です。
それら全てを、1つのディレクトリに保存します。
</p>

<p>
まず「config.xml」を用意します。
<pre class="code"><code>&lt;?xml version="1.0" encoding="UTF-8" ?&gt;
&lt;widget&gt;
  &lt;widgetname&gt;フォト蔵スライドショー&lt;/widgetname&gt;
  &lt;description&gt;フォト蔵で人気の写真を表示します&lt;/description&gt;
  &lt;id&gt;
    &lt;host&gt;photozou.jp&lt;/host&gt;
    &lt;name&gt;photozou-slideshow&lt;/name&gt;
    &lt;revised&gt;2009-06-17&lt;/revised&gt;
  &lt;/id&gt;
  &lt;width&gt;150&lt;/width&gt;
  &lt;height&gt;150&lt;/height&gt;
  &lt;author&gt;
    &lt;name&gt;YAMAOKA Hiroyuki&lt;/name&gt;
    &lt;organization&gt;Unoh Inc.&lt;/organization&gt;
  &lt;/author&gt;
&lt;/widget&gt;
</code></pre>
</p>

<p>
そして、「index.html」を用意します。
<pre class="code"><code>&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
  &lt;script type="text/javascript" src="http://www.google.com/jsapi"&gt;&lt;/script&gt;
  &lt;script type="text/javascript"&gt;google.load("jquery", "1.3");&lt;/script&gt;
  &lt;style type="text/css"&gt;
    ul#photos {
      position:relative;
      width:120px;
      height:120px;
      margin:0;
      padding:0;
      list-style:none;
    }
    ul#photos li {
      position:absolute;
      top:0;
      left:0;
      z-index:8;
    }
    ul#photos li.active {
      z-index:10;
    }
    ul#photos li.last-active {
      z-index:9;
    }
  &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;ul id="photos"&gt;&lt;/ul&gt;
&lt;script type="text/javascript"&gt;
$(function(){
  $.ajax({
    type: "GET",
    url: "http://api.photozou.jp/rest/photo_list_public",
    data: { type: "popular", limit: "30" },
    dataType: "xml",
    success: function(data, dataType) {
      if (data !== undefined) {
        var photosFirstChild = $("#photos li:first-child");
        var photos = $(data).find("photo").each(function() {
          var thumbnailURL = $(this).find("thumbnail_image_url").text();
          var url = $(this).find("url").text();
          var content = "&lt;a href=\""+ url +"\"&gt;&lt;img src=\"" + thumbnailURL + "\"&gt;&lt;/a&gt;";
          if (photosFirstChild.length == 0) {
            $("#photos").append($("&lt;li&gt;").html(content));
          } else {
            photosFirstChild.before($("&lt;li&gt;").html(content));
          }
        });
      }
    },
    error: function() { opera.postError("Photozou API error"); },
    complete: function(xmlHttpRequest, textStatus) {
      if (textStatus != "success") {
        return;
      }
      setInterval(function() {
        var activePhoto = $("#photos li.active");
        if (activePhoto.length == 0) {
          activePhoto = $("#photos li:last");
        }
        var nextPhoto = activePhoto.next().length ?
          activePhoto.next() : $("#photos li:first");
        activePhoto.addClass("last-active");
        nextPhoto.css({ opacity: 0.0 })
          .addClass("active")
          .animate({ opacity: 1.0 }, 1000, function() {
             activePhoto.removeClass("active last-active");
           });
      }, 5000);
    }
  });
});
&lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;
</code></pre>
</p>

<p>
JavaScriptを使うにあたって、<a href="http://jquery.com/">jQuery</a>をライブラリとして利用しています。
今回はGoogleの<a href="http://code.google.com/intl/ja/apis/ajaxlibs/">AJAX Libraries API</a>を利用しましたが、ウィジェットとして配布するのであればパッケージに同梱することになるでしょう。
スライドショーとして写真を順々に表示する部分は、
<a href="http://jonraasch.com/blog/a-simple-jquery-slideshow">A Simple jQuery Slideshow</a>を参考にしました。
</p>

<p>
Operaのウィジェットでは、通信はXMLHttpRequestを使って行います。
通常Ajaxなどで使う場合は同一のドメイン内にしかリクエストを発行できませんが、
ウィジェットの中で使う場合は何の制限もなく使うことができます。
今回は<a href="http://photozou.jp/basic/api_method_photo_list_public">フォト蔵のAPI</a>にリクエストを投げています。
</p>

<p>
この2つのファイルを同じディレクトリに保存して、
「config.xml」をOperaのウィンドウにドラッグ＆ドロップすると、
ウィジェットが起動します。簡単ですね。
まだ閉じるボタンも設定画面もありませんが、ウィジェットが1つできました。
実際にウィジェットとして配布する場合は、
zipでディレクトリをアーカイブして拡張子をwgtに変更すればいいようです。
</p>

<p>
非常に簡単に作れるので、個人的なツールをささっと作るといった用途にも使えそうです。
また、HTMLとJavaScriptで作成するので、JavaScriptの豊富なライブラリを利用できるのも大きなポイントです。
ちょっといろいろ作ってみるのもいいかもしれませんね。
</p>
]]>
        
    </content>
</entry>

<entry>
    <title>Django風ＰＨＰフレームワークPlufを試してみました</title>
    <link rel="alternate" type="text/html" href="http://labs.unoh.net/2009/06/djangopluf.html" />
    <link rel="service.edit" type="application/atom+xml" href="http://www.unoh.net/mt32/mt-atom.cgi/weblog/blog_id=2/entry_id=1647" title="Django風ＰＨＰフレームワークPlufを試してみました" />
    <id>tag:labs.unoh.net,2009://2.1647</id>
    
    <published>2009-06-14T15:37:50Z</published>
    <updated>2009-06-14T16:17:46Z</updated>
    
    <summary>最近マジクエストというアトラクションにはまっています。 Keitaです。 PHPには、CakePHPやsymfony、EthnaやrhacoとかCodeIgniterやPiece Frameworkなどなどいろいろフレームワークがありますが、探してみるとこういったよく耳にするフレームワークのほかにもいろいろなフレームワークがあります。 Do You PHP はてなの記事で知ったのですが、The Big List of PHP Frameworksといった記事も出ているようです。 最近では、RubyのSinatraライクなフレームワークもちょこちょこ出てきているようで、yamaokaが社内の勉強会にて発表してくれていました。 さて、そのThe Big List of PHP Frameworksの僕自身そのリストの膨大さに愕然としてまったくその内容やソースを追いかけていませんでしたのですが、...</summary>
    <author>
        <name>unoh</name>
        
    </author>
    
    <content type="html" xml:lang="ja" xml:base="http://labs.unoh.net/">
        <![CDATA[最近マジクエストというアトラクションにはまっています。<br />
Keitaです。<br />
<br />
PHPには、CakePHPやsymfony、EthnaやrhacoとかCodeIgniterやPiece Frameworkなどなどいろいろフレームワークがありますが、探してみるとこういったよく耳にするフレームワークのほかにもいろいろなフレームワークがあります。<br />
<br />
<a href="http://d.hatena.ne.jp/shimooka/20090108">Do You PHP はてな</a>の記事で知ったのですが、<a href="http://devreview.com/big-list-of-php-frameworks/">The Big List of PHP Frameworks</a>といった記事も出ているようです。<br />
<br />
最近では、RubyのSinatraライクなフレームワークもちょこちょこ出てきているようで、<a href="http://labs.unoh.net/cgi-bin/mt-search.cgi?tag=yamaoka">yamaoka</a>が社内の勉強会にて発表してくれていました。
<br />
さて、その<a href="http://devreview.com/big-list-of-php-frameworks/">The Big List of PHP Frameworks</a>の僕自身そのリストの膨大さに愕然としてまったくその内容やソースを追いかけていませんでしたのですが、先日偶然に「Django風のルーティングなフレームワークほしいなー」とか考えて探していたらPlufというフレームワークがひっかりましたので、簡単に試してみました。

<h2>Plufの特色</h2>
詳しくは<a href="http://www.pluf.org/doc/">公式のドキュメント</a>を見てもらうとしておおむねざっくりと僕の理解で書かせていただくと、Djangoの機能を「シンプル」に実装したPHPフレームワークというイメージです。<br />
なぜシンプルにという部分を強調したかというと、難しいと思われる機能をシンプルに実装することによって非常に高速に動作します。<br />
<br />
ここらへん難しいことをシンプルに行うというPHPの思想（？）にあってるなという印象があります。
また、Python風のコードを移植するとなんとなくコードの書き方もPython風にしたくなりますが、PHPの標準のコードともいえるPEAR風の書き方になっておりここらへんも個人的にはとてもポイントが高かったです。<br />

<h2>シンプルなコード</h2>
<pre class="code">
$ctl[] = array('regex' => '#^/hello$#',
               'base' => '/index.php',
               'model' => 'Unoh_Views',
               'method' => 'hello');
</pre>
<br />
<pre class="code">
class Unoh_Views 
{
    public function hello($req, $arg)
    {
        return new Pluf_HTTP_Response('Hello Pluf!');
    }
}
</pre>
<br />
ルーティングは正規表現で記述します。
正規表現や名前つきの正規表現をサポートします。

<pre class="code">
$ctl[] = array('regex' => '#^/sample/(<year>\d+)/$#',
               'base' => '/index.php',
               'model' => 'Unoh_Views',
               'method' => 'year');
</pre>
<br />
<pre class="code">
class Unoh_Views 
{
    public function hello($req, $arg)
    {
        return new Pluf_HTTP_Response($arg['year']);
    }

}
</pre>


<h2>テンプレート</h2>
テンプレートエンジンを独自（？）にもっており継承をサポートしています。
<pre class="code">
$ctl[] = array('regex' => '#^/template/$#',
               'base' => '/index.php',
               'model' => 'Unoh_Views',
               'method' => 'template');
</pre>
Views
<pre class="code">
    public function template($req, $arg)
    {
        return Pluf_Shortcuts_RenderToResponse('unoh/template.html',
                                                array('data' => 'data')
                                               );
    }
</pre>
<br />
Base.html(元になるHTML)
<pre class="code">
&lt;html&gt;
&lt;head&gt;
&lt;title&gt;{block title}{/block}&lt;/title&gt;
&lt;/head&gt;
&lt;a href="{url 'Unoh_Views::date', array('2009', '06')}"&gt;2009/06&lt;/a&gt;
{block body}{/block}
&lt;/html&gt;
</pre>
<br />
template.html(実際に表示するhtml)
<pre class="code">
{extends 'unoh/base.html'}

{block title}test{/block}
{block body}&lt;span style="color:red"&gt;ボディ&lt;/span&gt;{/block}
</pre>

またテンプレート関数のurlはディスパッチャの設定の指定を見てURLを生成してくれます。<br />

<h2>そのほか</h2>
formやバリデートを行うクラスや、ORマッパができるモデルやテストツールマイグレーションまでかなり幅広く用意されていますが今回は省略します。<br />


<h2>まとめ</h2>
PHPには追いきれないほどのフレームワークがあります。<br />
もちろんすべてを使う必要はありませんが、フレームワークにはそれぞれいろいろなノウハウが入っており、コードを読んで勉強するというは非常によいことかなと考えています。<br />
<br />
また有名どころではなくても面白いものがいっぱいありますので、是非、いろいろなフレームワークを試すととても勉強になりそうです。<br />
<br />
何かのご参考になれば幸いです。


]]>
        
    </content>
</entry>

<entry>
    <title>テスターの人権</title>
    <link rel="alternate" type="text/html" href="http://labs.unoh.net/2009/06/post_134.html" />
    <link rel="service.edit" type="application/atom+xml" href="http://www.unoh.net/mt32/mt-atom.cgi/weblog/blog_id=2/entry_id=1643" title="テスターの人権" />
    <id>tag:labs.unoh.net,2009://2.1643</id>
    
    <published>2009-06-01T09:40:31Z</published>
    <updated>2009-06-01T09:48:00Z</updated>
    
    <summary>こんにちは！やまもと＠テスト番長です。  最近、15年住み続けた築40年以上になる木造アパートから、築2年の駅近マンションに引越しました。 あまりの住環境の落差に、今までは何だったんだろうと呟いていたりします。 変化することを面倒臭がらずに、日々快適な暮らしを目指すべきだったんでしょうね。 さて、テスターが快適に仕事をするに当たって、保障されるべきこととは何でしょうか？ 「テスターの権利章典」というものを考えた方がいるのをご存知ですか？ 昨年秋に訳されたもので旬は逃しておりますが、最近ふと思い出したのでご紹介してみます。 テスターの権利章典 Tom Gilb &amp; Kai Gilb : Testers Bill of Rights こちらのページの下のほうに、大西建児さん訳の日本語版があります。 これはテスターの方のみならず、むしろプログラマの方に見ていただきたいなと思います。 お互いを尊重...</summary>
    <author>
        <name>unoh</name>
        
    </author>
    
        <category term="Tips" />
    
    <content type="html" xml:lang="ja" xml:base="http://labs.unoh.net/">
        <![CDATA[こんにちは！やまもと＠テスト番長です。 

最近、15年住み続けた築40年以上になる木造アパートから、築2年の駅近マンションに引越しました。
あまりの住環境の落差に、今までは何だったんだろうと呟いていたりします。
変化することを面倒臭がらずに、日々快適な暮らしを目指すべきだったんでしょうね。

さて、テスターが快適に仕事をするに当たって、保障されるべきこととは何でしょうか？
「テスターの権利章典」というものを考えた方がいるのをご存知ですか？
昨年秋に訳されたもので旬は逃しておりますが、最近ふと思い出したのでご紹介してみます。


テスターの権利章典
<a href="http://www.gilb.com/tiki-index.php?page=Testers%20Bill%20of%20Rights&structure=Community%20Pages">Tom Gilb & Kai Gilb : Testers Bill of Rights</a>

こちらのページの下のほうに、<a href="http://labo.mamezou.com/staff/onishi.html">大西建児さん</a>訳の日本語版があります。


これはテスターの方のみならず、むしろプログラマの方に見ていただきたいなと思います。
お互いを尊重し協業していく上で、相手にとって何が大事なのかを知っておくのは、とても重要なことだからです。

人権というとちょっと大袈裟かもしれませんが、大事にしたいことばかりですね。]]>
        
    </content>
</entry>

<entry>
    <title>5分で分かるHaml</title>
    <link rel="alternate" type="text/html" href="http://labs.unoh.net/2009/05/5haml.html" />
    <link rel="service.edit" type="application/atom+xml" href="http://www.unoh.net/mt32/mt-atom.cgi/weblog/blog_id=2/entry_id=1640" title="5分で分かるHaml" />
    <id>tag:labs.unoh.net,2009://2.1640</id>
    
    <published>2009-05-26T08:50:05Z</published>
    <updated>2009-05-29T01:33:45Z</updated>
    
    <summary>先日、まちつく！が正式リリースになりました。よろしければ是非携帯でアクセスして遊んでみてください。 こんにちは、ryosukeです。 ラボブログの前々回のエントリーで ruby で実装された web application framework の Sinatra が紹介されていたのですが、私もあまりのお手頃感に触発されて少しさわってみました。 その時にふとモデルやビューにいつもは使わない物を使ってみようと思い立ち、 Sequel と Haml を選んでみたのですが、 Haml の構文が見た目に反して(?)思いの他わかりやすかったので、今更感もありますが私同様 erb 以外使おうとも思わなかった人も少なくないのでは無いかと思いご紹介させて頂こうと思います。 Haml は XHTML Abstraction Markup Language の略で...という所から説明するのが筋なのですが、あっ...</summary>
    <author>
        <name>unoh</name>
        
    </author>
    
    <content type="html" xml:lang="ja" xml:base="http://labs.unoh.net/">
        <![CDATA[先日、<a href="http://mt9.jp/">まちつく！</a>が<a href="http://www.unoh.net/news/2009/05/post_83.html">正式リリース</a>になりました。よろしければ是非携帯でアクセスして遊んでみてください。

こんにちは、ryosukeです。

ラボブログの前々回のエントリーで ruby で実装された web application framework の Sinatra が紹介されていたのですが、私もあまりのお手頃感に触発されて少しさわってみました。

その時にふとモデルやビューにいつもは使わない物を使ってみようと思い立ち、 Sequel と Haml を選んでみたのですが、 Haml の構文が見た目に反して(?)思いの他わかりやすかったので、今更感もありますが私同様 erb 以外使おうとも思わなかった人も少なくないのでは無いかと思いご紹介させて頂こうと思います。

Haml は XHTML Abstraction Markup Language の略で...という所から説明するのが筋なのですが、あっという間に5分経過してしまいそうなので、要点をかいつまんで進めて行こうと思います。

<h2>まずは簡単に全体像を見る</h2>
以下のようにマークアップします。

<h3>Haml</h3>
<pre class="code">!!! XML
!!!
%html
  %head
    %meta{ 'http-equiv' => 'content', :content => 'text/html; charset=utf-8'}
  %body
    %div#container
      %a welcome to |
      labs.unoh.net |</pre>

<h3>出力</h3>
<pre class="code">&lt;?xml version='1.0' encoding='utf-8' ?&gt;
&lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"&gt;
&lt;html&gt;
  &lt;head&gt;
    &lt;meta content='text/html; charset=utf-8' http-equiv='content' /&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;div id='container'&gt;
      &lt;a&gt;welcome to labs.unoh.net&lt;/a&gt;
    &lt;/div&gt;
  &lt;/body&gt;
&lt;/html&gt;</pre>

<h2>XML宣言</h2>
<pre class="code">!!! XML</pre>
<pre class="code">&lt;?xml version='1.0' encoding='utf-8' ?&gt;</pre>
<strong>MEMO:</strong> 第二引数に文字コードを指定できる。デフォルトはUTF-8。
<pre class="code">!!! XML euc-jp</pre>
<pre class="code">&lt;?xml version='1.0' encoding='euc-jp' ?&gt;</pre>

<h2>文書型宣言</h2>
<pre class="code">!!!</pre>
<pre class="code">&lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"&gt;</pre>
<strong>MEMO:</strong> デフォルトでは XHTML 1.0 Transitional になります。他にもいくつか例示すると以下のように記述できます。
<pre class="code">!!! Strict</pre>
<pre class="code">!!! Frameset</pre>
<pre class="code">!!! 1.1</pre>

<h2>%要素名</h2>
<pre class="code">%a labs.unoh.net</pre>
<pre class="code">&lt;a&gt;labs.unoh.net&lt;/a&gt;</pre>
<strong>MEMO:</strong> 要素名を省略できる場面では DIV がデフォルトになる。

<h2>%要素名{:属性名 => '値'}</h2>
<pre class="code">%a{:href => 'http://labs.unoh.net/'} labs.unoh.net</pre>
<pre class="code">&lt;a href="http://labs.unoh.net/"&gt;labs.unoh.net&lt;/a&gt;</pre>
<strong>MEMO:</strong> %aと{}の間にスペースを空けない。シンボルに使えない文字を含めたい場合(http-equivの-等)は文字列にする。

<h2>要素のネスト</h2>
<pre class="code">%div
  %a{:href => 'http://labs.unoh.net/'} labs.unoh.net</pre>
<pre class="code">&lt;div&gt;
  &lt;a href='http://labs.unoh.net/'&gt;labs.unoh.net&lt;/a&gt;
&lt;/div&gt;</pre>
<strong>MEMO:</strong> ネストはインデントで表現する。 1インデントは2つのスペースで表現する。

<h2>複数行分割</h2>
<pre class="code">%span この行は長過ぎるので行分割したい |
パイプで行分割できるんです。 |</pre>
<pre class="code">&lt;span&gt;この行は長過ぎるので行分割したい パイプで行分割できるんです。&lt;/span&gt;</pre>
これはテキストだけでなく、構文中に含める事が出来ます。
<pre class="code">%script{ :type => "textjavascript", |
:src => "/js/jquery.js" } |</pre>
<strong>MEMO:</strong> |の前にスペースが必須。最終行の |を忘れないように。

<h2>コメント</h2>
<pre class="code">/ HTMLのコメント</pre>
<pre class="code">&lt;!-- HTMLのコメント --&gt;</pre>
<pre class="code">-# haml のコメント</pre>
<pre class="code">出力ドキュメントとしては表示されません</pre>

<h2>エスケープ</h2>
Hamlに取って意味のあるメタ文字をエスケープするには ¥ を使います。
<pre class="code">¥%a が &lt;a&gt;になります</pre>
<pre class="code">%a が &lt;a&gt;になります</pre>

<h2>ID</h2>
id は属性の一つなので {} で表現する事も可能ですが、 id と class は以下の様なショートカット構文が用意されています。
<pre class="code">%div#container body</pre>
<pre class="code">&lt;div id='container'&gt;body&lt;/div&gt;</pre>
<pre class="code">%div.navi menu</pre>
<pre class="code">&lt;div class='navi'&gt;menu&lt;/div&gt;</pre>

<h2>フィルター</h2>
:(コロン)+キーワードでインデントされたブロックにフィルタをかける事が出来ます。
フィルタは他にもいくつか用意されているので、マニュアルを見てください。
<h3>javascript</h3>
<pre class="code">%head
  :javascript
    var string = 'hello';
    window.alert(string);
</pre>
<pre class="code">&lt;head&gt
&lt;script type='text/javascript'&gt
    //&lt;![CDATA[
      var string = 'hello';
      window.alert(string);
    //]]&gt
  &lt;/script&gt
&lt;/head&gt
</pre>

<h3>escaped</h3>
<pre class="code">%div
  :escaped
    &lt;html&gt;
    &lt;head&gt;
    &lt;title&gt;title&lt;/title&gt;
    &lt;/head&gt;
    &lt;body&gt;
    &lt;h1&gt;h1&lt;/h1&gt;
    &lt;/body&gt;
    &lt;/html&gt;
</pre>
<pre class="code">&lt;div&gt;
  &amp;lt;html&amp;gt;
  &amp;lt;head&amp;gt;
  &amp;lt;title&amp;gt;title&amp;lt;/title&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
  &amp;lt;h1&amp;gt;H1&amp;lt;/h1&amp;gt;
  &amp;lt;/body&amp;gt;
  &amp;lt;/html&amp;gt;
&lt;/div&gt;
</pre>

<h2>rubyコードを埋め込む</h2>
= 以降に ruby コードを書くと、評価して出力してくれます。
<pre class="code">%div= "現在の時刻は" + Time.now.to_s + "です"</pre>
<pre class="code">&lt;div&gt;現在の時刻はTue May 26 17:28:15 +0900 2009です&lt;/div&gt;</pre>

<h2>制御構文</h2>
制御構文と題しましたが実際には ruby コードがかけます。 = との違いは評価結果を出力するか否かで、こちらは出力しないのでテンプレートとして使う場合は制御構文を書く場合が多いでしょう。

<pre class="code">- (1..10).each do |i|
  %p= "値は#{i}です。"
  - if i >= 5
    %span.big 5以上</pre>
<pre class="code">    &lt;p&gt;値は1です。&lt;/p&gt;
    &lt;p&gt;値は2です。&lt;/p&gt;
    &lt;p&gt;値は3です。&lt;/p&gt;
    &lt;p&gt;値は4です。&lt;/p&gt;
    &lt;p&gt;値は5です。&lt;/p&gt;
    &lt;span class='big'&gt;5以上&lt;/span&gt;
    &lt;p&gt;値は6です。&lt;/p&gt;
    &lt;span class='big'&gt;5以上&lt;/span&gt;
    &lt;p&gt;値は7です。&lt;/p&gt;
    &lt;span class='big'&gt;5以上&lt;/span&gt;
    &lt;p&gt;値は8です。&lt;/p&gt;
    &lt;span class='big'&gt;5以上&lt;/span&gt;
    &lt;p&gt;値は9です。&lt;/p&gt;
    &lt;span class='big'&gt;5以上&lt;/span&gt;
    &lt;p&gt;値は10です。&lt;/p&gt;
    &lt;span class='big'&gt;5以上&lt;/span&gt;
</pre>

いかがでしょうか？5分だと少しきついかもしれませんが、これを見ただけでもすぐ書けそうな気がしませんか？

ここまで Haml の基本的な構文をかいつまんでみてきましたが、できるだけ出力に近い方が何かと便利というのは正直な所で、プロダクションレベルや共同作業が必要になる場合に採用するかというとちょっと...と考えてしまうかも知れません。とはいえ HTML を理解していればとても簡単に Haml も覚える事ができるので、どんな物かを知る為にちょっと触ってみるのも良いのではないでしょうか。

また、 Haml には Sass という CSS テンプレートがあり、簡潔に記述したり、変数を使ったり、コードを再利用したり、出力されるCSSを圧縮したりと erb等のテンプレートシステム を使って CSS をメンテナンスした事がある人にとっては恐らく Haml 以上に魅力的なおすすめツールだと思います。今回 Sass は扱いませんでしたが、機会があれば Sass にも触れられればと思います。

<h2>参考サイト</h2>
<ul>
<li><a href="http://haml.hamptoncatlin.com/docs/rdoc/classes/Haml.html">Module haml</a></li>
<li><a href="http://haml.hamptoncatlin.com/docs/rdoc/classes/Sass.html">Module Sass</a></li>
</ul>]]>
        
    </content>
</entry>

<entry>
    <title>Software Design 6月号に「diffの動作原理を知る」の記事を執筆しました</title>
    <link rel="alternate" type="text/html" href="http://labs.unoh.net/2009/05/software_design_6diff.html" />
    <link rel="service.edit" type="application/atom+xml" href="http://www.unoh.net/mt32/mt-atom.cgi/weblog/blog_id=2/entry_id=1637" title="Software Design 6月号に「diffの動作原理を知る」の記事を執筆しました" />
    <id>tag:labs.unoh.net,2009://2.1637</id>
    
    <published>2009-05-18T01:18:00Z</published>
    <updated>2009-05-25T09:49:29Z</updated>
    
    <summary> 最近、「何故、君はマウスを2つ同時に使っているんだい？」と聞かれることが多くなったbokkoです。VX Revolution RXは右手用ですが、左手で使うならMicrosoftのArcがオススメです。近頃はフットマウスを買うかどうか真剣に悩んでいます。 Software Designには去年にも「ソースを読み，パッチを作成してみよう～GNU GLOBAL，diff，patchの使い方～」という記事を執筆する機会を頂いたので、本誌に執筆するのはこれが二度目になります。 今回の内容は以前当ブログに書いた「diff with C++」の記事をもっと濃くした感じになっていて、編集距離やLCS、SESの解説に始まり、Subversionのdiffエンジンや拙作のdtlで使われているO(NP)差分アルゴリズムについて解説しています。 O(NP)や、それによく似たO(ND)をはじめとするエディットグ...</summary>
    <author>
        <name>unoh</name>
        
    </author>
    
        <category term="Lua" />
    
        <category term="アルゴリズム" />
    
    <content type="html" xml:lang="ja" xml:base="http://labs.unoh.net/">
        <![CDATA[
最近、「何故、君はマウスを2つ同時に使っているんだい？」と聞かれることが多くなったbokkoです。VX Revolution RXは右手用ですが、左手で使うならMicrosoftのArcがオススメです。近頃はフットマウスを買うかどうか真剣に悩んでいます。


Software Designには去年にも<a href="http://gihyo.jp/magazine/SD/archive/2008/200810">「ソースを読み，パッチを作成してみよう～GNU GLOBAL，diff，patchの使い方～」</a>という記事を執筆する機会を頂いたので、本誌に執筆するのはこれが二度目になります。


今回の内容は以前当ブログに書いた「<a href="http://labs.unoh.net/2008/11/diff_with_c.html">diff with C++</a>」の記事をもっと濃くした感じになっていて、編集距離やLCS、SESの解説に始まり、<a href="http://subversion.tigris.org/">Subversion</a>のdiffエンジンや拙作の<a href="http://code.google.com/p/dtl-cpp/">dtl</a>で使われているO(NP)差分アルゴリズムについて解説しています。


O(NP)や、それによく似たO(ND)をはじめとするエディットグラフを使って差分を求めるアルゴリズムは普段何気なく使っているdiffコマンドや各種バージョン管理システムで使われていることからもわかるようにとても有用であり、アルゴリズム自体の美しさやスマートさにも目を見張るものがあります。興味がある方はぜひ手にとって読んでみてください。本記事を通してdiffのアルゴリズムのおもしろさが伝われば幸いです。


<span class="mt-enclosure mt-enclosure-image" style="display: inline;"><a href="http://gihyo.jp/magazine/SD/archive/2009/200906"><img alt="sd0906cover.jpgのサムネール画像のサムネール画像" src="http://labs.unoh.net/assets_c/2009/05/sd0906cover-thumb-250x353-11-thumb-250x353-15.jpg" width="250" height="353" class="mt-image-none" style="" /></a></span>


<h3>お詫びと訂正</h3>


本記事には訂正箇所が3箇所ほどあります。うち2つは本記事で使用している<a href="http://code.google.com/p/dtl-cpp/">dtl</a>のバージョンが執筆後に0.05から0.06に上がったことによるもので、もう一つはO(NP)のアルゴリズムの計算量に対する私の勘違いによるものです。詳しい内容については<a href="http://gihyo.jp/magazine/SD/archive/2009/200906/support">Software Design 2009年6月号：サポートページ｜gihyo.jp ... 技術評論社</a>をご覧下さい。


関係者や読者の方にはご迷惑をおかけしました。すみません。以後、気を付けます。


<h3>おまけ</h3>

本記事にはサンプルコードとしてO(NP)を使って2つの文字列間の編集距離を求めるC++のプログラムが載っているのですが、ふと思い立って、同じことをするプログラムおよびLCSとSESも計算するプログラムをLuaで書いてみました。だからどうというわけでもないのですが、何か新しいプログラミング言語を触る際に、こんな風に自分にとってなじみのあるアルゴリズムを記述してみるのはその言語を勉強する上で、とてもいい方法だと再確認した次第です。


実際に書いていてハマったのですが、Luaではほかの大多数の言語と違って配列や文字列のインデックスが0からではなく1から始まるので、別の言語で記述されたアルゴリズムとかのコードを写経する際は注意しましょう。


しかし、この間、プログラマの友達とそんなLuaの独特の挙動について話をしていると、

<blockquote>
<em>まあ、(アルゴリズムの)論文に載っているような擬似コードは配列のインデックスが1から始まってるから、論文の擬似コードを写経する分にはちょうどいいんじゃね？</em>
</blockquote>

という感じのことを言われて「ああ、言われてみればそうだなあ」と妙に納得した次第です。とりあえず、今後、論文に載ってる疑似コードを実際のコードに落とす際はまずLuaで書いてそれからCとかC++に書き直すということを実践してみようと思います。


話が逸れました。以下にLuaによるO(NP)アルゴリズムの実装を2種類示します。その1が編集距離のみを計算するバージョン、その2が編集距離に加えてLCS、SESを計算するバージョンになります。なお、<a href="http://code.google.com/p/dtl-cpp/">dtl</a>と違ってワーストケース(LCSが極端に短くなる場合)への対応は行っていません。

<h3>LuaによるO(NP)の実装 その1(編集距離のみ)</h3>

<pre class="code">
-- editDistance.lua
-- Lua5.1.4で動作確認
ONP = {}
function ONP.new (a, b)           -- ONPクラスのコンストラクタ
   local self = {                           -- メンバ変数
      A = a,
      B = b,
      M = string.len(a),
      N = string.len(b),
   }
   function self.editDistance ()  -- 編集距離を計算する
      offset = self.M + 1
      delta  = self.N - self.M
      size   = self.M + self.N + 3
      fp = {}
      for i = 0, size-1 do
	 fp[i] = -1
      end
      p = -1
      repeat
	 p = p + 1
	 for k=-p, delta-1, 1 do
	    fp[k+offset] = self.snake(k, math.max(fp[k-1+offset]+1, fp[k+1+offset]))
	 end
	 for k=delta+p,delta+1, -1 do
	    fp[k+offset] = self.snake(k, math.max(fp[k-1+offset]+1, fp[k+1+offset]))
	 end
	 fp[delta+offset] = self.snake(delta, math.max(fp[delta-1+offset]+1, fp[delta+1+offset]))
      until fp[delta+offset] >= self.N
      return delta + 2 * p
   end
   function self.snake (k, y)     -- 最遠点のy座標を計算する
      x = y - k
      while (x < self.M and y < self.N and 
	     string.sub(self.A, x+1, x+1) == string.sub(self.B, y+1, y+1)) 
      do
	 x = x + 1
	 y = y + 1
      end
      return y
   end
   if self.M >= self.N then       -- N >= Mになるように調整
      self.A, self.B = self.B, self.A
      self.M, self.N = self.N, self.M
   end
   return self
end
if #arg < 2 then
   error("few argument")
end
a = arg[1]
b = arg[2]
d = ONP.new(a, b)
print("editDistance:" .. d:editDistance())
</pre>

<h4><strong>実行</strong></h4>

<pre class="code">
narazuya@bokkko% lua editDistance.lua abcdef dacfea
6
narazuya@bokkko% lua e.editDistancelua abc abd
2
narazuya@bokkko% 
</pre>

<h3>LuaによるO(NP)の実装 その2(編集距離、LCS、SES)</h3>

<pre class="code">
-- onp.lua
-- Lua5.1.4で動作確認
ONP = {}
SES_DELETE = -1
SES_COMMON = 0
SES_ADD    = 1
function ONP.new (a, b)          -- ONPクラスのコンストラクタ
   local self = {                          -- メンバ変数
      A = a,
      B = b,
      M = string.len(a),
      N = string.len(b),
      path       = {},
      pathposi   = {},
      P          = {},
      ses        = {},
      seselem    = {},
      lcs        = "",
      editdis    = 0,
      reverse    = false,
   }
   -- getter
   function self.geteditdistance () 
      return self.editdis
   end
   function self.getlcs ()
      return self.lcs
   end
   function self.getses ()
      return self.ses
   end
   -- constructor
   function self.P.new (x_, y_, k_)
      local self = { x=x_, y=y_, k=k_ }
      return self
   end
   function self.seselem.new (elem_, type_)
      local self = { elem=elem_, type=type_}
      return self
   end
   -- 差分構築
   function self.compose ()
      offset = self.M + 1
      delta  = self.N - self.M
      size   = self.M + self.N + 3
      fp = {}
      for i = 0, size-1 do
	 fp[i]        = -1
	 self.path[i] = -1
      end
      p = -1
      repeat
	 p = p + 1
	 for k=-p, delta-1, 1 do
	    fp[k+offset] = self.snake(k, fp[k-1+offset]+1, fp[k+1+offset])
	 end
	 for k=delta+p,delta+1, -1 do
	    fp[k+offset] = self.snake(k, fp[k-1+offset]+1, fp[k+1+offset])
	 end
	 fp[delta+offset] = self.snake(delta, fp[delta-1+offset]+1, fp[delta+1+offset])
      until fp[delta+offset] >= self.N
      self.editdis = delta + 2 * p
      r    = self.path[delta+offset]
      epc  = {}
      while r ~= -1 do
	 epc[#epc+1] = self.P.new(self.pathposi[r+1].x, self.pathposi[r+1].y, nil)
	 r = self.pathposi[r+1].k
      end
      self.recordseq(epc)
   end
   function self.snake (k, p, pp)     -- 最遠点のy座標を計算する
      r = 0;
      if p > pp then
	 r = self.path[k-1+offset];
      else
	 r = self.path[k+1+offset];
      end
      y = math.max(p, pp);
      x = y - k
      while (x < self.M and y < self.N and 
	     string.sub(self.A, x+1, x+1) == string.sub(self.B, y+1, y+1)) 
      do
	 x = x + 1
	 y = y + 1
      end
      self.path[k+offset] = #self.pathposi
      p = self.P.new(x, y, r)
      self.pathposi[#self.pathposi+1] = p
      return y
   end
   function self.recordseq (epc)          -- LCS、SESを記録する
      x_idx,  y_idx  = 1, 1
      px_idx, py_idx = 0, 0
      for i=#epc, 1, -1 do
	 while (px_idx < epc[i].x or py_idx < epc[i].y) do
	    if (epc[i].y - epc[i].x) > (py_idx - px_idx) then
	       elem = string.sub(self.B, y_idx, y_idx)
	       if self.reverse then 
		  type = SES_DELETE
	       else
		  type = SES_ADD
	       end
	       self.ses[#self.ses+1] = self.seselem.new(elem, type)
	       y_idx  = y_idx  + 1
	       py_idx = py_idx + 1
	    elseif epc[i].y - epc[i].x < py_idx - px_idx then
	       elem = string.sub(self.A, x_idx, x_idx)
	       if self.reverse then 
		  type = SES_ADD
	       else
		  type = SES_DELETE
	       end
	       self.ses[#self.ses+1] = self.seselem.new(elem, type)
	       x_idx  = x_idx  + 1
	       px_idx = px_idx + 1
	    else 
	       elem = string.sub(self.A, x_idx, x_idx)
	       type = SES_COMMON
	       self.lcs = self.lcs .. elem
	       self.ses[#self.ses+1] = self.seselem.new(elem, type)
	       x_idx  = x_idx  + 1
	       y_idx  = y_idx  + 1
	       px_idx = px_idx + 1
	       py_idx = py_idx + 1
	    end
	 end
      end
   end
   if self.M >= self.N then       -- N >= Mになるように調整
      self.A, self.B = self.B, self.A
      self.M, self.N = self.N, self.M
      self.reverse = true
   end
   return self
end
if #arg < 2 then
   error("few argument")
end
a = arg[1]
b = arg[2]
d = ONP.new(a, b)
d:compose()
print("editDistance:" .. d:geteditdistance()) -- 編集距離
print("LCS:"          .. d:getlcs())                    -- Longest Common Subsequence
print("SES")
ses = d:getses()                                          -- Shortest Edit Script
for i=1, #ses do
   if ses[i].type == SES_COMMON then
      print("  " .. ses[i].elem)
   elseif ses[i].type == SES_DELETE then
      print("- " .. ses[i].elem)
   elseif ses[i].type == SES_ADD then
      print("+ " .. ses[i].elem)
   end
end
</pre>

<h4><strong>実行</strong></h4>

<pre class="code">
narazuya@bokkko% lua onp.lua abcdef dacfea
editDistance:6
LCS:acf
SES
+ d
  a
- b
  c
- d
- e
   f
+ e
+ a
narazuya@bokkko% lua a.lua abc abd
editDistance:2
LCS:ab
SES
  a
  b
- c
+ d
narazuya@bokkko%
</pre>]]>
        
    </content>
</entry>

<entry>
    <title>Sinatra気に入った</title>
    <link rel="alternate" type="text/html" href="http://labs.unoh.net/2009/05/sinatra.html" />
    <link rel="service.edit" type="application/atom+xml" href="http://www.unoh.net/mt32/mt-atom.cgi/weblog/blog_id=2/entry_id=1636" title="Sinatra気に入った" />
    <id>tag:labs.unoh.net,2009://2.1636</id>
    
    <published>2009-05-15T09:12:16Z</published>
    <updated>2009-05-15T09:34:10Z</updated>
    
    <summary>先日、まちつく！が正式リリースになりました。よろしければ是非携帯でアクセスして遊んでみてください。 おはようございます。内田です。 今までRailsを使うほどでも無いアプリはオレオレフレームワークで作ってたのですが、最近巷で流行ってるsinatraのコードを読んでみたら必要十分な機能があり、センスも良く、とても気に入りました。 今回は公式ドキュメントの流れで、使いそうな機能をまとめてみました 一番簡単な例 sudo gem install sinatra # app.rb require &apos;rubygems&apos; require &apos;sinatra&apos; get &apos;/&apos; do   &apos;Hello, world&apos; end ruby app.rb curl http://localhost:4567/ Routes HTTPメソッドにURLとブロックを渡します get &apos;/&apos; do end post &apos;/...</summary>
    <author>
        <name>unoh</name>
        
    </author>
    
    <content type="html" xml:lang="ja" xml:base="http://labs.unoh.net/">
        <![CDATA[<p>先日、<a href="http://mt9.jp/">まちつく！</a>が<a href="http://www.unoh.net/news/2009/05/post_83.html">正式リリース</a>になりました。よろしければ是非携帯でアクセスして遊んでみてください。</p>

おはようございます。内田です。

今までRailsを使うほどでも無いアプリはオレオレフレームワークで作ってたのですが、最近巷で流行ってるsinatraのコードを読んでみたら必要十分な機能があり、センスも良く、とても気に入りました。

今回は公式ドキュメントの流れで、使いそうな機能をまとめてみました

<h2>一番簡単な例</h2>
<pre class="code">
sudo gem install sinatra
</pre>

<pre class="code">
# app.rb
require 'rubygems'
require 'sinatra'

get '/' do
  'Hello, world'
end
</pre>

<pre class="code">
ruby app.rb
curl http://localhost:4567/
</pre>

<h2>Routes</h2>
HTTPメソッドにURLとブロックを渡します
<pre class="code">
get '/' do
end
post '/' do
end
put '/' do
end
delete '/' do
end
</pre>

パラメータを含めたURLも可能
<pre class="code">
get '/hello/:name' do
  "Hello #{params[:name]}"
end
</pre>

上記パラメータはブロック引数でも取れる
<pre class="code">
get '/hello/:name' do |n|
  "Hello #{n}"
end
</pre>

ワイルドカードもつかえます
<pre class="code">
get '/say/*/to/*' do
  # /say/hello/to/worldにアクセス
  params["splat"] # => ["hello", "world]
end
</pre>

?つきのパラメータやPOST等のパラメータはparams[:xxx]で取得できるよ

アプリケーションファイルは複数に分けることが可能。
読み込みにはrequireを使わずにloadを使うとdevelopmentモードの時に便利
<pre class="code">
require 'rubygems'
require 'sinatra'
get '/' do
  "Hello world"
end
load 'more_routes.rb'
</pre>

<pre class="code">
# more_routes.rb
get '/foo' do
  "Foo"
end
</pre>

<h2>Handlers</h2>

Redirect
<pre class="code">
redirect '/'
redirect 'http://www.google.co.jp'
redirect '/', 303
redirect '/', 307
</pre>


Session
クッキーベースのセッション
有効にするにはTOPレベルか、configureブロックに書く
<pre class="code">
enable :sessions
get '/' do
  session["count"] ||= 0
  session["count"] += 1
  "count: #{session['count']}"
end
</pre>

Status
通常では200番のステータスコードになるがstatusメソッドをつかうと変更できる
<pre class="code">
get '/' do
  status 404
  "Not found"
end
</pre>

<h2>Filters</h2>
イベントの前に実行される
<pre class="code">
before do
end
</pre>

<pre class="code">
before do
  new_params = {}
  params.each_pair do |full_key, value|
    this_param = new_params
    split_keys = full_key.split(/\]\[|\]|\[/)
    split_keys.each_index do |index|
      break if split_keys.length == index + 1
      this_param[split_keys[index]] ||= {}
      this_param = this_param[split_keys[index]]
   end
   this_param[split_keys.last] = value
  end
  request.params.replace new_params
end
</pre>

<pre class="code">
<form>
  <input ... name="post[title]" />
  <input ... name="post[body]" />
  <input ... name="post[author]" />
</form>
</pre>

<pre class="code">
{"post"=>{ "title"=>"", "body"=>"", "author"=>"" }}
</pre>


<h2>views</h2>
Template Languages
viewファイルはroot/viewsに置きましょう
Haml,Sass,Erb,Builderがつかえる
<pre class="code">
# app.rb
get '/' do
  erb :index     # views/index.erb
  sass :styles   # views/styles.sass
  haml :index    # views/index.haml
  builder :index # views/index.builder
end
</pre>

builderはブロックを使ってRSSを出力できたりする
サンプルプログラムが公式にあります。

Layouts
root/views/layout.{erb|haml|builder}
<pre class="code">
<html>
<body>
<%= yield %>
</body>
</html>
</pre>

使いたくないときは
<pre class="code">
get '/' do
  erb :index, layout => false
end
</pre>

アプリケーションと同じファイルにviewが書ける
が、個人的には使わない。公式参照


<h2>Models</h2>
sinatraはmodelを提供してないので、好きなのを使うとよろし
Sequelを使ってみる。
<pre class="code">
# app.rb
require 'rubygems'
require 'sinatra'
require 'sequel'
Sequel::Model.plugin(:schema)
sequel.connect('sqlite://test.db')
class Items < Sequel::Model
  unless table_exista?
    set_schema do
      primary_key :id
      string :name
      timestamp :created_at
    end
    create_table
  end
end
get '/' do
  @items = Items.all
  erb :index
end
</pre>

<pre class="code">
# views/index.erb
<% for item in @items %>
  <div><%= item.name %></div>
<% end %>
</pre>

DatamapperやActiveRecordを使いたい場合は公式参照


API的なJSONでも出力してみる
<pre class="code">
require 'json'
get '/api/items.json' do
  content_type :json
  JSON.unparse(Items.all.map{|e|e.values})
end
</pre>


<h2>Helpers</h2>
helpersブロックでメソッドを定義するとイベント内やテンプレートで使えます
<pre class="code">
helpers do
  def bar(name)
    "#{name}bar"
  end
end
get '/:name' do
  bar(params[:name])
end
</pre>

Rails的なpartialを定義
<pre class="code">
helpers do
  def partial(page, options={})
    erb page, options.merge!(:layout => false)
  end
end
</pre>

escape_html等のaliasを定義しとくと便利
<pre class="code">
helpers do
  include Rack::Urils
  alias_method :h, :escape_html
  alias_method :u, :escape
end
</pre>

<h2>Rack Middleware</h2>
useメソッドで指定しなさい
<pre class="code">
use Rack::Lint
get '/' do
  "hello"
end
</pre>

Sinatra自体がいくつか読み込んでるので、重複するかもしれません。
Loggerとか。1リクエストでログが複数件でてビックリしました。


<h2>Error Handling</h2>
not_found
定義されてないURLにアクセスがあった場合に動作
<pre class="code">
not_found do
  "Not found"
end
</pre>

しかし、デフォルトのメッセージがセンス良すぎなので、production時にのみ定義したい。

error
例外が投げられたら動作
<pre class="code">
error do
  'error - ' + request.env['sinatra.error'].name
end
error MyCustomError do
  'So what happened was...' + request.env['sinatra.error'].message
end
</pre>

これもセンス良すぎなので次の方法でproduction時のみ動作にしましょう
configureメソッドをつかいます
<pre class="code">
configure :production do
  not_found do
    "Not found"
  end
  error do
    "Error"
  end
end
</pre>


<h2>Configuration</h2>
configureブロックの中で変数を使う場合はsetをつかいましょう
<pre class="code">
configure :development do
  set :dbname, 'devdb'
end
configure :production do
  set :dbname, 'productiondb'
end
get '/whatdb' do
  'We are using the database named ' + options.dbname
end
</pre>


<h2>Deploy</h2>
個人的にはpassengerが良い
アプリケーションのrootディレクトリにtmp,publicディレクトリを作りconfig.ruを書く
<pre class="code">
#config.ru
require 'app'
run Sinatra::Application
</pre>

最新バージョンのrack(1.0)やpassennger(2.0.3)を使ったときのバグが公式に書いてありますので参照下さい。


<h2>参考</h2>
http://www.sinatrarb.com/
http://www.sinatrarb.com/book.html
http://www.sinatrarb.com/faq.html
http://www.sinatrarb.com/testing.html
http://www.sinatrarb.com/extensions.html

おしまい]]>
        
    </content>
</entry>

<entry>
    <title>ImageMagickのテキスト描画を画像にしてコストダウン</title>
    <link rel="alternate" type="text/html" href="http://labs.unoh.net/2009/05/imagemagick.html" />
    <link rel="service.edit" type="application/atom+xml" href="http://www.unoh.net/mt32/mt-atom.cgi/weblog/blog_id=2/entry_id=1633" title="ImageMagickのテキスト描画を画像にしてコストダウン" />
    <id>tag:labs.unoh.net,2009://2.1633</id>
    
    <published>2009-05-14T10:28:39Z</published>
    <updated>2009-05-14T10:35:38Z</updated>
    
    <summary><![CDATA[こんにちは。中村です。 本日、まちつく！が正式リリースになりました。よろしければ是非携帯でアクセスして遊んでみてください。以前公開しました位置情報ライブラリも利用されています。 さて、PHPでImageMagickを使って画像生成を行うときに、どうもテキストを描画すると無視できないコストがかかることに最近気が付きました。具体的には次のようにImagickDraw::drawImageメソッドによる描画コストです。 &lt;?php $draw = new ImagickDraw(); $draw-&gt;setFontSize(20); $draw-&gt;setFillColor('#FFFFFF'); $draw-&gt;annotation(20, 28, 'Hello World'); for ($i = 0; $i &lt; 1000; $i++) {     $img = ne...]]></summary>
    <author>
        <name>unoh</name>
        
    </author>
    
    <content type="html" xml:lang="ja" xml:base="http://labs.unoh.net/">
        <![CDATA[<p>こんにちは。中村です。</p>
<p>本日、<a href="http://mt9.jp/">まちつく！</a>が<a href="http://www.unoh.net/news/2009/05/post_83.html">正式リリース</a>になりました。よろしければ是非携帯でアクセスして遊んでみてください。以前公開しました<a href="http://labs.unoh.net/2008/08/phpgeomobilejp_converter.html">位置情報ライブラリ</a>も利用されています。</p>
<p>さて、PHPでImageMagickを使って画像生成を行うときに、どうもテキストを描画すると無視できないコストがかかることに最近気が付きました。具体的には次のようにImagickDraw::drawImageメソッドによる描画コストです。</p>
<pre class="code"><code>&lt;?php
$draw = new ImagickDraw();
$draw-&gt;setFontSize(20);
$draw-&gt;setFillColor('#FFFFFF');
$draw-&gt;annotation(20, 28, 'Hello World');

for ($i = 0; $i &lt; 1000; $i++) {
    $img = new Imagick();
    $img-&gt;newImage(160, 40, new ImagickPixel('#550000'));
    $img-&gt;drawImage($draw);
    $img-&gt;setImageFormat('jpeg');
    $img-&gt;writeImage('test.jpg');
}</code></pre>
<p>生成される画像は次のようになります。</p>
<a href="http://photozou.jp/photo/show/784/21255361"><img src="http://art5.photozou.jp/pub/784/784/photo/21255361.jpg" alt="helloworld" width="160" height="40" style="border:0" /></a><br /><a href="http://photozou.jp/photo/show/784/21255361">helloworld</a> posted by <a href="http://photozou.jp/user/top/784">(C)フォト蔵</a>
<p>これを実行すると手元の環境では次のようになりました。</p>
<pre class="code"><code>% /usr/bin/time -p php test.php
real 12.81
user 12.07
sys 0.71</code></pre>
<p>フリーな文字列を生成する場合には使えないのですが、出現する文字やフォントサイズなどが限られている場合には、これを画像に置き換えることで大分と速度が上がります。次の例では「Hello」と「World」の2つの画像を描画しています。</p>
<pre class="code"><code>&lt;?php
$hello = new Imagick('hello.jpg');
$world = new Imagick('world.jpg');
for ($i = 0; $i &lt; 1000; $i++) {
    $img = new Imagick();
    $img-&gt;newImage(160, 40, new ImagickPixel('#550000'));
    $img-&gt;compositeImage($hello, Imagick::COMPOSITE_OVER, 20, 15);
    $img-&gt;compositeImage($world, Imagick::COMPOSITE_OVER, 65, 15);
    $img-&gt;setImageFormat('jpeg');
    $img-&gt;writeImage('test.jpg');
}</code></pre>
<pre class="code"><code>% /usr/bin/time -p php test.php
real 2.08
user 1.94
sys 0.13</code></pre>
<p>約13秒から約2秒にまでなりました。ただし、10文字の描画に2枚の画像では不公平な感じもしますので、それぞれ文字単位に画像にして描画してみました。</p>
<pre class="code"><code>&lt;?php
$h = new Imagick('h.jpg');
$e = new Imagick('e.jpg');
$l = new Imagick('l.jpg');
$o = new Imagick('o.jpg');
$w = new Imagick('w.jpg');
$r = new Imagick('r.jpg');
$d = new Imagick('d.jpg');

for ($i = 0; $i &lt; 1000; $i++) {
    $img = new Imagick();
    $img-&gt;newImage(160, 40, new ImagickPixel('#550000'));
    $img-&gt;compositeImage($h, Imagick::COMPOSITE_OVER, 20, 15);
    $img-&gt;compositeImage($e, Imagick::COMPOSITE_OVER, 30, 15);
    $img-&gt;compositeImage($l, Imagick::COMPOSITE_OVER, 40, 15);
    $img-&gt;compositeImage($l, Imagick::COMPOSITE_OVER, 50, 15);
    $img-&gt;compositeImage($o, Imagick::COMPOSITE_OVER, 60, 15);
    $img-&gt;compositeImage($w, Imagick::COMPOSITE_OVER, 80, 15);
    $img-&gt;compositeImage($o, Imagick::COMPOSITE_OVER, 90, 15);
    $img-&gt;compositeImage($r, Imagick::COMPOSITE_OVER, 100, 15);
    $img-&gt;compositeImage($l, Imagick::COMPOSITE_OVER, 110, 15);
    $img-&gt;compositeImage($d, Imagick::COMPOSITE_OVER, 120, 15);
    $img-&gt;setImageFormat('jpeg');
    $img-&gt;writeImage('test.jpg');
}</code></pre>
<pre class="code"><code>/usr/bin/time -p php test.php
real 2.31
user 2.12
sys 0.15</code></pre>
<p>小さな画像が増えてもそれほどコストが上がることはないようです。</p>
<p>大量に画像を生成するようなケースで、かつ文字のパターンが少ない場合に限られますが、速度が必要な場合にはテキスト描画を画像に置き換えた方が有利のようです。</p>
<p>いずれにしても、自分の利用する環境でのベンチが大切だと思いますので、参考程度に見てもらえればと思います。</p>]]>
        
    </content>
</entry>

<entry>
    <title>Luaを設定ファイルとして使う</title>
    <link rel="alternate" type="text/html" href="http://labs.unoh.net/2009/04/lua.html" />
    <link rel="service.edit" type="application/atom+xml" href="http://www.unoh.net/mt32/mt-atom.cgi/weblog/blog_id=2/entry_id=1625" title="Luaを設定ファイルとして使う" />
    <id>tag:labs.unoh.net,2009://2.1625</id>
    
    <published>2009-04-30T10:13:22Z</published>
    <updated>2009-05-11T10:09:52Z</updated>
    
    <summary> テイルズオブヴェスペリアがPS3に移植されると聞いて、今年中にPS3を買うことを固く決意したbokkoです。部屋に置き場所がないとか、社会人になってから積みゲーがどんどん増えているとか、随分前にXBOX360版の総プレイ時間が三桁になっていることはこの際気にしないことにします。あと、機会があればCellとdtlを使って編集距離の計算をやってみたいです(多分あんまり速くない)。 Lua Luaは軽量で高速なインタプリタ言語です。コアは非常に小さいのですが、テーブルというデータ構造や関数がファーストオブジェクトであることを利用して、本来は機能としてないオブジェクト指向言語のような書き方をしたり、独自に拡張したりと、なかなかパワフルな言語です。実際の使われ方としてはアプリケーションに組み込んで使うことが多く、組み込み言語などと呼ばれることもあるようです。今回は、LuaのプログラムをC、C++で...</summary>
    <author>
        <name>unoh</name>
        
    </author>
    
        <category term="C、C++" />
    
    <content type="html" xml:lang="ja" xml:base="http://labs.unoh.net/">
        <![CDATA[
テイルズオブヴェスペリアがPS3に移植されると聞いて、今年中にPS3を買うことを固く決意したbokkoです。部屋に置き場所がないとか、社会人になってから積みゲーがどんどん増えているとか、随分前にXBOX360版の総プレイ時間が三桁になっていることはこの際気にしないことにします。あと、機会があればCellと<a href="http://code.google.com/p/dtl-cpp/">dtl</a>を使って編集距離の計算をやってみたいです(多分あんまり速くない)。

<h3>Lua</h3>

Luaは軽量で高速なインタプリタ言語です。コアは非常に小さいのですが、テーブルというデータ構造や関数がファーストオブジェクトであることを利用して、本来は機能としてないオブジェクト指向言語のような書き方をしたり、独自に拡張したりと、なかなかパワフルな言語です。実際の使われ方としてはアプリケーションに組み込んで使うことが多く、組み込み言語などと呼ばれることもあるようです。今回は、LuaのプログラムをC、C++で書かれたアプリケーションの設定ファイルとして使う方法を解説します。

<h3>コンパイラ言語と設定ファイル</h3>

元々、Luaを設定ファイルとして使おうと思ったのは私が趣味で書いているC++のプログラムに設定ファイルが必要という理由からでした。最初は自分でファイルをテキトーに解析して、値を取得していく感じでいいかなと思ったのですが、やっぱり後々の保守性を考えるとちゃんとしたライブラリとか形式(XMLとかYAMLとかJSONとか)を使った方がいいと判断して手段を模索しました。インタプリタ言語ならそれで書かれたファイル自体を設定ファイルにしてしまっても特に問題ないのですが、C++のようなコンパイラ言語でconfig.hとかconfig.cとかを作ってそれを書き換える形式だと、設定を書き換える度にアプリケーションをコンパイルし直さなければなりません。そこで小さい言語処理系みたいなものを組み込んでそれ自体を設定ファイルとして扱ってみようと考えました。SWIGを使うことも考えましたが、*.iファイルなるものがたくさんできるのは(個人的に)なんだか嫌だったので、やめました。

<h3>設定ファイルの保守性と拡張性</h3>

そして、できるだけ書きやすく、簡単に使えて、しかも簡単に拡張できそうだという理由でLuaを採用しました。どういうことかと言うと、例えば、アプリケーションのエンコーディングを設定ファイルに記述するとしましょう。

<pre class="code">
encoding="utf-8"
</pre>

これなら、わざわざLuaを組み込んだりしなくてもささっとできそうです。しかし、後になってそのアプリケーションに何かしらのブラックリストみたいなものが必要になったとしましょう。

<pre class="code">
ignore_strs = {
   ".svn",
   ".git",
   "CVS",
   ".DS_Store",
   ".gitignore",
   "GTAGS",
   "GRTAGS",
   "GPATH",
   "GSYMS",
}
</pre>

例えば、バージョン管理システムだと上記のように特定のファイルを操作対象から外すように設定ファイルを記述することかと思います。これでもそれほど難しくはないでしょう。しかし、さらにある時、以下のように何かしらの重み付けされた各エントリを保持するパラメータが欲しくなったとしましょう。

<pre class="code">
priority = {
   server1 = 5,
   server2 = 10,
   server3 = 3,
   server4 = 2,
   server5 = 9,
}
</pre>

できないことはないけど、段々ややこしくなってきました。さらにさらに上記の各エントリが複数の値を保持したくなったとしましょう。

<pre class="code">
priority = {
  server1 = {weight = 5,  sub = "server2"},
  server2 = {weight = 10, sub = "server3"},
  server3 = {weight = 3,  sub = "server4"},
  server4 = {weight = 2,  sub = "server5"},
  server5 = {weight = 9,  sub = "server1"},
}
</pre>

と、こんな風にどんどんデータ構造が複雑になり、段々アプリケーションとは直接関係ないこと、ネストは最大何回までにしようとか、テンプレートを使えるようにしようかとか、変数を使えるようにしようとか考えるようになっていくのです。そして最初のコードは単に1行毎に解析するだけ構造になっていたせいで、それを1回だけネストすることが可能なプログラムに書き直すことになり、そしてそして自分が決めた最大回数だけネストできるようにプログラムを拡張することになり、そしてそしてそしてある程度階層が深くなるとなんだかうまくいかなくなることがわかり、とりあえず動く間に合わせのコードを書き、しばらくして誰も拡張・保守できないようなコードが出来上がるのです。そうでなくても例えば過去のバージョンの互換性などのために、エレガントさを捨てて、愚直なことをしなければいけないというケースはたくさんあると思います。

もちろん、設定ファイルを解析するプログラムを将来を見越して柔軟で拡張性の高いコードで記述するようにすればいいと考えるかも知れません。しかし、それは言うのは簡単ですが、実際にそれをやるのは非常に難しいと思います。というのも我々はいわゆるレキシテキケイイ(棒読み)という理由で、構文がありそうで実は全然構文がなく、また、一貫性がありそうで実は全くない設定ファイルを持つアプリケーションを何度も見ていて、しかもそれを普段からまるでそんなことは気にしないふりをして使っているのです。気にしないふりができるのはそのアプリケーションに関連するドキュメントや書籍がしっかり整備されているからなのです。

そしてどうしようもなくなった後、運が良ければ最初からまともな構文を持った小さな言語処理系を実装するか、既にあるそのような処理系を組み込んで、それを設定ファイルして扱うようになる・・・かもしれません。というのもそのアプリケーションが既にどこかで動いていたりすると、過去のバージョンの設定ファイルのと互換性を考えたりしないといけなくなって、事実上不可能になる可能性があります。

ちなみに、

上記のコードは全てLuaでそのまま処理できます
上記のコードは全てLuaでそのまま処理できます
上記のコードは全てLuaでそのまま処理できます

大事なことなので3回言いました。

<h3>C言語からLuaの変数の値を取得する</h3>

前置きが長くなりました。それではC、C++からLuaのファイルを読み込んで、Luaの変数や関数をC、C++から使う例を紹介していきます。まず、以下のようにエンコーディングパラメータを記述したLuaのプログラムがあるとします。

<h4>config.lua</h4>
<pre class="code">
encoding = "utf-8"
</pre>

この値を取得して出力するC言語のプログラムは以下のようになります。(エラー処理は簡略化のため省いています)

<h4>config.c</h4>
<pre class="code">
#include &lt;stdio.h&gt;
#include &lt;lua.h&gt;
#include &lt;lualib.h&gt;
#include &lt;lauxlib.h&gt;
int main (int argc, char *argv[]) {
  lua_State* L = luaL_newstate();                   /*  Luaオブジェクトの生成 */
  luaL_openlibs(L);                                         /*  標準ライブラリの読み込み */
  luaL_dofile(L, "config.lua");                          /*  Luaファイルの評価 */
  lua_getglobal(L, "encoding");                       
  const char *encoding = lua_tostring(L, -1);   /*  変数の値を取得 */
  lua_pop(L, 1);                                               /*  スタックから変数をポップする */
  printf("encoding:%s\n", encoding);
  lua_close(L);                                                 /*  Luaオブジェクトを解放 */
  return 0;
}
</pre>

<h4>実行</h4>

<pre class="code">
$ gcc config.c -I(ヘッダファイルへのパス) liblua.a
$ ./a.out
encoding:utf-8
$
</pre>

と、こんな風に非常に簡単にC言語と連携することができるのがLuaの特徴の一つです。

<h3>C言語からLuaの関数を呼び出す</h3>

続いて、C言語からLuaの関数を呼び出す例を紹介します。以下のようにnの階乗を返す関数をLua側で定義します。

<h4>fact.lua</h4>
<pre class="code">
function fact (n)
   if n == 1 then
      return 1
   end
   return n * fact(n - 1)
end
</pre>

上記のfact関数を呼び出すC言語のプログラムは以下のようになります。

<h4>cfrom.lua</h4>
<pre class="code">
#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;lua.h&gt;
#include &lt;lualib.h&gt;
#include &lt;lauxlib.h&gt;
int main (int argc, char *argv[]) {
  if (argc < 2) {
    fprintf(stderr, "few argument\n");
    return -1;
  }
  int n = atoi(argv[1]);
  lua_State* L = luaL_newstate();            /*  Luaオブジェクトの生成 */
  luaL_openlibs(L);                                  /*  標準ライブラリの読み込み */
  luaL_dofile(L, "fact.lua");                       /*  Luaファイルの評価 */
  lua_getglobal(L, "fact");                         /*  fact関数を取得 */
  lua_pushinteger(L, n);                           /*  fact関数の引数を設定 */
  lua_pcall(L, 1, 1, 0);                               /*  fact関数を呼び出す */
  int ret = lua_tointeger(L, -1);                  /*  fact関数の返り値を取得 */
  lua_pop(L, 1);                                        /*  値をスタックから除去 */
  printf("fact(%d) = %d\n", n, ret);
  lua_close(L);                                          /*  Luaオブジェクトを解放 */
  return 0;
}
</pre>

変数を取り出すのとさほど変わらないのがわかると思います。共通しているのはpushやpopという単語から分かるように、Lua側の変数や関数をC言語側から使用する際にLuaが用意しているスタックから値を取り出すということです。

<h4>実行</h4>

<pre class="code">
$ gcc cfromlua.c -I(ヘッダファイルへのパス)  liblua.a
$ ./a.out 5
fact(5) = 120
$
</pre>

<h3>設定ファイルの値のマッチング</h3>

最後に、コマンドラインから与えた引数が設定ファイルに記述された文字列や正規表現にマッチするか調べるプログラムを紹介します。実はLuaは単体で文字列だけでなく、正規表現も扱うことができます。

<h4>ignores.lua</h4>

以下は無視する文字列および正規表現を記述した設定ファイルです。

<pre class="code">
-- 無視する文字列
ignore_strs = {
   ".svn",
   ".hg",
   ".git",
   "CVS",
   ".DS_Store",
   ".gitignore",
   ".hgignore",
   "GTAGS",
   "GRTAGS",
   "GPATH",
   "GSYMS",
}
-- 無視する正規表現のパターン
ignore_patterns = {
   "%.o$",
   "%.a$",
}
</pre>

<h4>match.lua</h4>

これはマッチング処理を行うLuaプログラムです。

<pre class="code">
function str_match (str)
   if (ignore_strs[str]) then
      return true
   end
   return false
end
function reg_match (str)
   for i, reg in pairs(ignore_patterns) do
      if (string.match(str, reg))
      then
         return true
      end
   end
   return false
end
</pre>

<h4>reverse.lua</h4>

そしてこれが元のignore.luaに書かれた設定を高速に解釈するためのちょっとしたプログラムになります。単に添字と値を逆にしたテーブルを返すだけです。

<pre class="code">
function reverse_arr (arr)
   ret = {}
   for i, val in pairs(arr) do
      ret[val] = i
   end
   return ret
end
</pre>

<h4>match_test.cpp</h4>

それではこれらのLuaのプログラムと以下のC++のプログラムを使ってマッチングを行うプログラムを書いてみます。C++だとLuaのヘッダファイルはextern "C"で囲む必要があるので、注意しましょう。

<pre class="code">
#include &lt;iostream&gt;
#include &lt;string&gt;
extern "C" {
#include &lt;lua.h&gt;
#include &lt;lualib.h&gt;
#include &lt;lauxlib.h&gt;
}
using namespace std;
bool isIgnoreStr (const string str, lua_State *L);
bool isIgnorePattern (const string str, lua_State *L);
bool isIgnoreStr (const string str, lua_State *L) {
  lua_getglobal(L, "str_match");
  lua_pushstring(L, str.c_str());
  lua_pcall(L, 1, 1, 0);
  int b = lua_toboolean(L, -1);
  lua_pop(L, 1);
  if (b) {
    return true;
  }
  return false;
}
bool isIgnorePattern (const string str, lua_State *L) {
  lua_getglobal(L, "reg_match");
  lua_pushstring(L, str.c_str());
  lua_pcall(L, 1, 1, 0);
  int b = lua_toboolean(L, -1);
  lua_pop(L, 1);
  if (b) {
    return true;
  }
  return false;
}
int main (int argc, char *argv[]) {
  if (argc < 2) {
    cerr << "few argument" << endl;
    return -1;
  }
  string str(argv[1]);
  lua_State* L = luaL_newstate();
  luaL_openlibs(L);
  luaL_dofile(L, "ignores.lua");
  luaL_dofile(L, "match.lua");
  luaL_dofile(L, "reverse.lua");
  luaL_dostring(L, "ignore_strs = reverse_arr(ignore_strs)");
  bool is_ignore_str = false;
  bool is_ignore_pattern = false;;
  if (isIgnoreStr(str, L)) {
    cout << str << " is ignore str." << endl;
    is_ignore_str = true;
  }
  if (isIgnorePattern(str, L)) {
    cout << str << " is ignore pattern." << endl;
    is_ignore_str = true;
  }
  if (!is_ignore_str && !is_ignore_pattern) {
    cout << str << " is not match." << endl;
  }
  lua_close(L);
  return 0;
}
</pre>

<h4>実行</h4>

<pre class="code">
$ g++ match_test.cpp -I(ヘッダファイルへのパス)  liblua.a
$ ./a.out abc
abc is not match.
$ ./a.out GPATH
GPATH is ignore str.
$ ./a.out GPATHd
GPATHd is not match.
$ ./a.out liblua.o
liblua.o is ignore pattern.
$ ./a.out liblua.a
liblua.a is ignore pattern.
$ ./a.out liblua.ac
liblua.ac is not match.
$ 
</pre>


<h3>まとめ</h3>

LuaのプログラムをC、C++アプリケーションの設定ファイルとして扱うということをやってみました。上記のようにLuaの構造体に対してちょっとスタックを操作するプログラムを書くだけなので、直接設定ファイルを解析するプログラムを書く場合に比べてプログラムを大幅に簡略化することができます。もちろん、C、C++側からLuaのプログラムを呼び出すためのボトルネックというのは当然あるのですが、<a href="http://shootout.alioth.debian.org/u32q/benchmark.php?test=all&lang=lua&lang2=yarv&box=1">Lua自体が高速</a>なこともあり、これは多くの場合、無視できると思います。


<h3>参考文献・URL</h3>

<ul>
<li><a href="http://www.amazon.co.jp/exec/obidos/ASIN/4797342722/unoh-22/ref=nosim/">入門Luaプログラミング</a></li>
<li><a href="http://alpha.mixi.co.jp/blog/?p=236">Lua on Tyrant: DBサーバにLLを組み込む</a></li>
<li><a href="http://www.hakkaku.net/articles/20081126-288">八角研究所 : Series: 高速スクリプト言語Luaを始めよう(5)</a></li>
</ul>


<h3>追記(2009/5/11)</h3>

gccでコンパイルする際に指定するファイルの順番が間違っていたので、修正しました。
]]>
        
    </content>
</entry>

<entry>
    <title>OpenSocialを始めよう！第1回</title>
    <link rel="alternate" type="text/html" href="http://labs.unoh.net/2009/04/opensocial1.html" />
    <link rel="service.edit" type="application/atom+xml" href="http://www.unoh.net/mt32/mt-atom.cgi/weblog/blog_id=2/entry_id=1622" title="OpenSocialを始めよう！第1回" />
    <id>tag:labs.unoh.net,2009://2.1622</id>
    
    <published>2009-04-23T06:05:40Z</published>
    <updated>2009-04-23T06:08:41Z</updated>
    
    <summary><![CDATA[こんにちは五十川です。 先日、gooホームとmixiアプリが立て続けに公開され、いよいよ日本でもOpenSocialが注目を集めるようになりました。そこで今回は、これからOpenSocialアプリケーションの開発を始めるにあたっての基礎的な内容をまとめてみたいと思います。 OpenSocialとは Google Code - OpenSocialの冒頭には、OpenSocial は複数のウェブサイト間で使用可能な、ソーシャル アプリケーションの共通 APIの定義であると書かれています。 あるOpenSocial対応のウェブサイトで動作するプログラムは、他のOpenSocial対応アプリケーションでも動作する &mdash; 例えばmixiアプリ用のプログラムは、gooホームやMySpaceなどでもそのまま動作する可能性があるというのがOpenSocialの重要な点です。もちろんウェブサイト...]]></summary>
    <author>
        <name>unoh</name>
        
    </author>
    
    <content type="html" xml:lang="ja" xml:base="http://labs.unoh.net/">
        <![CDATA[<p>こんにちは五十川です。</p>

<p>先日、<a href="http://developer.home.goo.ne.jp/">gooホーム</a>と<a href="http://developer.mixi.co.jp/">mixiアプリ</a>が立て続けに公開され、いよいよ日本でもOpenSocialが注目を集めるようになりました。そこで今回は、これからOpenSocialアプリケーションの開発を始めるにあたっての基礎的な内容をまとめてみたいと思います。</p>

<h3>OpenSocialとは</h3>

<p><a href="http://code.google.com/intl/ja/apis/opensocial/">Google Code - OpenSocial</a>の冒頭には、<q cite="http://code.google.com/intl/ja/apis/opensocial/">OpenSocial は複数のウェブサイト間で使用可能な、ソーシャル アプリケーションの共通 API</q>の定義であると書かれています。</p>

<p>あるOpenSocial対応のウェブサイトで動作するプログラムは、他のOpenSocial対応アプリケーションでも動作する &mdash; 例えばmixiアプリ用のプログラムは、gooホームやMySpaceなどでもそのまま動作する可能性があるというのがOpenSocialの重要な点です。もちろんウェブサイトごとに細部の実装は異なる場合があり、まったく同じプログラムがすべてのプラットフォームでそのまま動作するとは限りませんが、各ウェブサイトが独自の規格を用意していて、それぞれにいちから開発しなければならない状況に比べれば遥かに少ない労力で、多くのプラットフォームに対応できるというのは、実に魅力的です。</p>

<h3>OpenSocial対応SNS</h3>

<p><a href="http://wiki.opensocial.org/index.php?title=Containers">OpenSocial Community Wiki - Containers</a>に、OpenSocial対応SNSの一覧があります。各SNSの「details」をクリックしたページでは、SNSごとの特徴や、開発にあたっての注意点などの概要が記載されています。クロスプラットフォームでの開発の際には、対象となるSNSについて、あらかじめ目を通しておくとよいでしょう。ちなみに、<a href="http://code.google.com/p/opensocial-resources/wiki/ComplianceTests">OpenSocial Compliance Tests</a>にあるテストガジェットを使って、SNSの実装の互換性を詳しく確認できたりもします。</p>

<h3>JavaScript APIとRESTful API</h3>

<p>下図はopensocial.orgに掲載されているOpenSocialの概要図です。中央に位置している緑の部分が、mixiやgooホームなどのSNSプラットフォームです。左の青いグループはSNSのウェブページ上に表示されるアプリケーション群です（これらはガジェットと呼ばれます）。OpenSocialは外部のウェブサイトやデスクトップアプリケーションなどからSNSのデータを利用することも想定されています。右の赤い部分がこれに当たります。</p>

<div><a href="http://photozou.jp/photo/show/784/20361757"><img src="http://art7.photozou.jp/pub/784/784/photo/20361757.png" alt="OpenSocial Overview" width="450" height="174" /></a></div>
<div><a href="http://www.opensocial.org/photo/opensocial-overview">OpenSocial Overview</a></div>

<p>ガジェットの基本的な実体はXMLです。XMLはガジェットの設定情報などと、実際に表示されるコンテンツとなるHTMLやJavaScript、CSSなどで構成されます。SNSのOpenSocialコンテナがこのXMLを処理したコンテンツが、SNSのウェブページ中のIFRAME内に表示されます。こうしたガジェットがSNSとのやり取りを行うためのAPIが、OpenSocialのJavaScript APIです。</p>

<p>一方、外部サイトやデスクトップアプリケーションなど、SNSの外部からSNSのデータにアクセスするために用意されているのが、OpenSocialのRESTful APIです。あるいは、例えば携帯電話のような、JavaScriptが利用できない端末向けのウェブサイトやアプリケーションもRESTful APIを利用することになるでしょう。携帯電話端末専用のSNSであるgumiが提供している<a href="http://gu3.jp/platform.html">gumi Platform</a>は、この例です。</p>

<h3>OpenSocialコンテナ</h3>

<p>SNSの本体とガジェットや外部のアプリケーションとのやり取りを仲介し、あるいはガジェットXMLを処理してウェブページにコンテンツを表示するといった、OpenSocialの中核として機能するのがコンテナです。</p>

<p>OpenSocialコンテナは、ガジェットとSNS本体とのやり取りのみならず、ガジェットとSNSの外部とのやり取りも仲介します。複雑なガジェットは往々にして外部のサーバとやり取りをする必要が生じますが、通常のJavaScriptでは外部リソースへのアクセスの際に生じるクロスドメインの問題は、コンテナがプロキシとして動作することで、ガジェットのプログラミングでは意識する必要がありません。あるいは、コンテナはガジェットXMLを含む各種リソースをキャッシュし、これらの配信サーバへのアクセスの負荷を軽減するといった役割も担います。</p>

<h3>Shindig!</h3>

<p>OpenSocialはその名前の通りオープンであり、コンテナの実装もオープンソースでの開発が行われています。それがApache Incubatorによる<a href="http://incubator.apache.org/shindig/">Shindig</a>です。ウェブサイトはShindigを採用することで、自前でいちからコンテナを実装する労力を省いてOpenSocialに対応することができます。実際、現在OpenSocialに対応しているSNSの多くがShindigを採用しています。また、開発者にとっては、ソースが公開されているので、それに目を通すことでコンテナ実装の理解を深めることができます。現時点でのShindigにはJava/Servlet版とPHP版があります。</p>

<h3>ガジェットXML</h3>

<p>以下はシンプルなガジェットXMLの例です。</p>
<p>このガジェットは、SNSのプロフィールページ（PROFILEビュー）で表示された場合、「これはPROFILEビューです。こんにちは○○さん」と表示します。○○の部分には、そのページを見ているユーザ（VIEWER）のニックネームが入ります。</p>

<p>ガジェットXMLはModule要素をルートとして、ガジェットの設定などが記述されたModulePrefs要素と、HTMLやJavaScriptなどによるコンテンツが記述されたContent要素で構成されます（この構造はiGoogleのガジェットXMLと同じです）。</p>

<pre class="code">&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
&lt;Module&gt;
  &lt;ModulePrefs
    title=&quot;テスト&quot;
    description=&quot;お試しガジェット&quot;
    author_email=&quot;test@example.com&quot;&gt;
    &lt;Require feature=&quot;opensocial-0.8&quot;/&gt;
  &lt;/ModulePrefs&gt;
  &lt;Content type=&quot;html&quot; view=&quot;profile&quot;&gt;&lt;![CDATA[
    &lt;script type=&quot;text/javascript&quot;&gt;
    gadgets.util.registerOnLoadHandler(function() {

      var req = opensocial.newDataRequest();

      // VIEWERの情報をリクエストする
      req.add(req.newFetchPersonRequest('VIEWER'), 'viewer');

      // リクエストを実行
      req.send(function(response) {

        // VIEWERの情報を取得
        var viewer = response.get('viewer').getData();
        var msg = 'こんにちは' + viewer.getDisplayName() + 'さん';
        document.getElementsByTagName('p').item(0)
          .appendChild(document.createTextNode(msg));
      });
    });
    &lt;/script&gt;
    &lt;p&gt;これはPROFILEビューです。&lt;/p&gt;
  ]]&gt;&lt;/Content&gt;
  &lt;Content type=&quot;html&quot; view=&quot;home&quot;&gt;&lt;![CDATA[
    &lt;p&gt;これはHOMEビューです。&lt;/p&gt;
  ]]&gt;&lt;/Content&gt;
  &lt;Content type=&quot;html&quot; view=&quot;canvas&quot;&gt;&lt;![CDATA[
    &lt;p&gt;これはCANVASビューです。&lt;/p&gt;
  ]]&gt;&lt;/Content&gt;
&lt;/Module&gt;</pre>

<p>ご覧のように、Content要素の中身は通常のウェブコンテンツと同様のHTMLです。違いは、JavaScriptプログラミングにおいてOpenSocial JavaScript API及びGadgets APIが利用可能である点と、通常のJavaScriptプログラミングとはいくつか異なる手段を用いる必要がある点です。</p>

<p>コンテナがこのXMLを処理して出力するHTMLは、HTMLのBODY要素内に、XMLのContent要素の中身が埋め込まれたものになります。</p>

<h3>ビュー</h3>

<p>上のXMLにはContent要素が3ヶあり、view属性値がそれぞれ、profile、home、canvasとなっています。</p>

<p>SNSウェブサイトにはガジェットが表示されるページがいくつかあります。ガジェットが表示されるページを「ビュー」と呼びます。OpenSocial標準では以下の4ヶのビューが定義されています（ただし、すべてのSNSでこの4ヶが存在しているわけではありません。例えば、gooホームにはPREVIEWビューはありません）。</p>

<p>HOMEビューはいわゆるマイページ/マイホームです。PROFILEビューはプロフィールページです。HOMEビューとPROFILEビューでは、ガジェットは通常サイドバーの一部などサイズの小さいエリアに、他のガジェットと共に表示されます。CANVASビューでは（共通ヘッダ/フッタなどを除く）画面全体にガジェットが表示されます。PREVIEWビューはガジェットのインストールページなど、ガジェットのデモを表示するページです（PREVIEWビューではVIEWERやOWNERの情報は取得できません）。</p>

<p>Content要素のview属性値にビュー名を指定すると、そのContent内のHTMLは指定したビューで表示されます。複数のビューで共通のContentは「view=&quot;home,profile&quot;」のようにビュー名をカンマ区切りで記述します。例えば、homeとprofileで共通のJavaScriptコードはContent@view=&quot;home,profile&quot;に記述しておき、それぞれで異なる部分をContent@view=&quot;home&quot;とContent@view=&quot;profile&quot;にそれぞれ記述するということが可能です。</p>

<h3>スタイル指定の注意点</h3>

<p>Content要素内のHTMLには、もちろんCSSも記述することができます。ガジェットはIFRAME内に表示されるので、SNS側のスタイル指定を意識する必要はありません。なお、<a href="http://code.google.com/apis/gadgets/docs/spec.html#gadgetrenderingrequest">ガジェットの仕様</a>では、コンテナの出力するHTMLは<q cite="http://code.google.com/apis/gadgets/docs/spec.html#gadgetrenderingrequest">ブラウザ固有のモード</q>（Quirksモード）で描画されると規定されていますので、スタイル指定を行う際は注意してください。</p>

<hr />

<p>第1回はここまでです。次回は以下のような話題を取り上げると思います。多分。</p>

<ul>
  <li>OpenSocialの開発環境について</li>
  <li>JavaScript API/Gadgets API</li>
  <li>ガジェットと外部サーバの連携</li>
</ul>

<p>ではでは</p>]]>
        
    </content>
</entry>

<entry>
    <title>Google App Engine Java 触ってみたメモ</title>
    <link rel="alternate" type="text/html" href="http://labs.unoh.net/2009/04/google_app_engine_java.html" />
    <link rel="service.edit" type="application/atom+xml" href="http://www.unoh.net/mt32/mt-atom.cgi/weblog/blog_id=2/entry_id=1617" title="Google App Engine Java 触ってみたメモ" />
    <id>tag:labs.unoh.net,2009://2.1617</id>
    
    <published>2009-04-15T05:24:13Z</published>
    <updated>2009-04-15T05:47:03Z</updated>
    
    <summary>yukiです。 最近社内のコードレビューにて「Javaとっかかり」というものがあり、とても興味があったので参加させて頂いたのですが(開いてもらったとも言う)、「なんとなくActionScriptに似てるなぁ」という印象を持ちました。Javaの人もActionScriptの人も「全然違う」と思うかもしれませんが。 そんな感じで日曜スクリプト書いてみたり色々勉強中のJavaですが、先日Google App EngineでもJavaが対応した(今は英語版のみ)ということなので、ちょっとイジってみました。PythonでWebアプリのエントリでも書いていますが、何かを作ると言うよりはイジってみたかったので。 まだ何かをアップロードしたわけではありませんが、流れをざっとご紹介しようと思います。 まずHelp→SoftwareUpdateからロケーションを追加し、Google Plugin for Ec...</summary>
    <author>
        <name>unoh</name>
        
    </author>
    
        <category term="レポート" />
    
    <content type="html" xml:lang="ja" xml:base="http://labs.unoh.net/">
        <![CDATA[<p>yukiです。<br />
最近社内のコードレビューにて「Javaとっかかり」というものがあり、とても興味があったので参加させて頂いたのですが(開いてもらったとも言う)、「なんとなくActionScriptに似てるなぁ」という印象を持ちました。Javaの人もActionScriptの人も「全然違う」と思うかもしれませんが。<br />
そんな感じで日曜スクリプト書いてみたり色々勉強中のJavaですが、先日<a href="http://code.google.com/intl/ja/appengine/">Google App Engine</a>でもJavaが対応した(今は英語版のみ)ということなので、ちょっとイジってみました。<a href="http://labs.unoh.net/2009/03/pythonweb.html">PythonでWebアプリ</a>のエントリでも書いていますが、何かを作ると言うよりはイジってみたかったので。<br />
まだ何かをアップロードしたわけではありませんが、流れをざっとご紹介しようと思います。
</p>
<p>まずHelp→SoftwareUpdateからロケーションを追加し、Google Plugin for Eclipseを導入しました。詳細は2009/04/15現在以下のようになっています。</p>
<ul>
<li>Google App Engine Java SDK 1.2.0</li>
<li>Google Plugin for Eclipse 3.4</li>
<li>Google Web Toolkit SDK 1.6.4</li>
</ul>
<p>次に新プロジェクトを作成します。先ほどのプラグインを追加すると新たに項目が増え「Web Application Project」というのが選択できるようになっています。<br />
<a href="http://photozou.jp/photo/show/784/20106250"><img src="http://art8.photozou.jp/pub/784/784/photo/20106250.jpg" alt="icon" width="240" height="133" style="border:0" /></a><br /><a href="http://photozou.jp/photo/show/784/20106250">icon</a> posted by <a href="http://photozou.jp/user/top/784">(C)フォト蔵</a><br />
<a href="http://photozou.jp/photo/show/784/20106247"><img src="http://art3.photozou.jp/pub/784/784/photo/20106247.jpg" alt="newproject" width="240" height="150" style="border:0" /></a><br /><a href="http://photozou.jp/photo/show/784/20106247">newproject</a> posted by <a href="http://photozou.jp/user/top/784">(C)フォト蔵</a></p>
<p>作成されたファイルは以下のような構造になっています。<br />
<a href="http://photozou.jp/photo/show/784/20106251"><img src="http://art2.photozou.jp/pub/784/784/photo/20106251.jpg" alt="directories" width="232" height="240" style="border:0" /></a><br /><a href="http://photozou.jp/photo/show/784/20106251">directories</a> posted by <a href="http://photozou.jp/user/top/784">(C)フォト蔵</a><br />
ここからServletやJSPを利用するコードを開発していきます。<br />
deployもEclipseから出来るようになっており、非常に簡単です。ただこのままですと以下のようなエラーが発生してdeployできません。
<pre class="code"><code>Creating staging directory
Scanning for jsp files.
Scanning files on local disk.
Initiating update.
Unable to upload:
java.io.IOException: Error posting to URL: http://appengine.google.com/api/appversion/create?app_id=XXXXXXXXXX&version=XXXXXXXX&
400 Bad Request
Invalid runtime specified.

	at com.google.appengine.tools.admin.ServerConnection.send(ServerConnection.java:114)
	at com.google.appengine.tools.admin.ServerConnection.post(ServerConnection.java:66)
	at com.google.appengine.tools.admin.AppVersionUpload.send(AppVersionUpload.java:345)
	at com.google.appengine.tools.admin.AppVersionUpload.beginTransaction(AppVersionUpload.java:159)
	at com.google.appengine.tools.admin.AppVersionUpload.doUpload(AppVersionUpload.java:68)
	at com.google.appengine.tools.admin.AppAdminImpl.update(AppAdminImpl.java:41)
	at com.google.appengine.eclipse.core.proxy.AppEngineBridgeImpl.deploy(AppEngineBridgeImpl.java:203)
	at com.google.appengine.eclipse.core.deploy.DeployProjectJob.runInWorkspace(DeployProjectJob.java:97)
	at org.eclipse.core.internal.resources.InternalWorkspaceJob.run(InternalWorkspaceJob.java:38)
	at org.eclipse.core.internal.jobs.Worker.run(Worker.java:55)
java.io.IOException: Error posting to URL: http://appengine.google.com/api/appversion/create?app_id=XXXXXXXXXX&version=XXXXXXXX&
400 Bad Request
Invalid runtime specified.</code></pre>
これは事前に認証が必要すればdeployできるようになるので、こちらから認証して下さい。しばらくすると(半日～1日程度)認可のメールが送られてきますので、そこからは問題なくdeployすることができます。<br/>
<a href="http://photozou.jp/photo/show/784/20106254"><img src="http://art4.photozou.jp/pub/784/784/photo/20106254.jpg" alt="deploy" width="240" height="78" style="border:0" /></a><br /><a href="http://photozou.jp/photo/show/784/20106254">deploy</a> posted by <a href="http://photozou.jp/user/top/784">(C)フォト蔵</a><br />
<a href="http://photozou.jp/photo/show/784/20106256"><img src="http://art8.photozou.jp/pub/784/784/photo/20106256.jpg" alt="app_id" width="240" height="209" style="border:0" /></a><br /><a href="http://photozou.jp/photo/show/784/20106256">app_id</a> posted by <a href="http://photozou.jp/user/top/784">(C)フォト蔵</a><br />
余談ですが、公式ページでは
<blockquote>April 2009: During this "early look" of Google App Engine for Java, the ability to upload Java applications to App Engine is reserved for a limited number of developer accounts. All users will be able to upload Java apps when we do a full release of Google App Engine for Java in the near future.</blockquote>
とあるので、しばらく待てば認証もなくなるのではないかと思います。</p>
<p>色々試行錯誤しながらやった結果、なかなか日本語での情報が少ないので大変ですが、おもしろそうなので何か作ってみるかもしれません。それではまた。</p>]]>
        
    </content>
</entry>

<entry>
    <title>IEでlabelの子要素に画像を含める</title>
    <link rel="alternate" type="text/html" href="http://labs.unoh.net/2009/04/label-img-fix-for-ie.html" />
    <link rel="service.edit" type="application/atom+xml" href="http://www.unoh.net/mt32/mt-atom.cgi/weblog/blog_id=2/entry_id=1616" title="IEでlabelの子要素に画像を含める" />
    <id>tag:labs.unoh.net,2009://2.1616</id>
    
    <published>2009-04-08T08:47:35Z</published>
    <updated>2009-04-08T08:58:13Z</updated>
    
    <summary><![CDATA[yamaokaです。 HTMLのフォームでチェックボックスやラジオボタンを扱う場合、 対応したlabel要素を用意することが多いと思います。 &lt;input type="checkbox" id="foo" name="foo"> &lt;label for="foo">   キャプション &lt;/label> わざわざ小さなチェックボックスやラジオボタンを狙ってクリックする必要がなくなり、 labelタグで囲まれたキャプションの部分をクリックすればよいようになります。 しかし、次のような場合どうでしょう。 &lt;input type="checkbox" id="foo" name="foo"> &lt;label for="foo">   &lt;img src="bar.gif">   キャプション &lt;/label> label要素の中にimg要素（画像）が含まれている...]]></summary>
    <author>
        <name>unoh</name>
        
    </author>
    
        <category term="JavaScript" />
    
    <content type="html" xml:lang="ja" xml:base="http://labs.unoh.net/">
        <![CDATA[<p>yamaokaです。</p>

<p>
HTMLのフォームでチェックボックスやラジオボタンを扱う場合、
対応したlabel要素を用意することが多いと思います。
<pre class="code"><code>&lt;input type="checkbox" id="foo" name="foo">
&lt;label for="foo">
  キャプション
&lt;/label>
</code></pre>
わざわざ小さなチェックボックスやラジオボタンを狙ってクリックする必要がなくなり、
labelタグで囲まれたキャプションの部分をクリックすればよいようになります。
</p>

<p>
しかし、次のような場合どうでしょう。
<pre class="code"><code>&lt;input type="checkbox" id="foo" name="foo">
&lt;label for="foo">
  &lt;img src="bar.gif">
  キャプション
&lt;/label>
</code></pre>
label要素の中にimg要素（画像）が含まれているような場合を考えてみます。
</p>

<p>
もっと具体的にいうと、ユーザーを選択する画面でそれぞれのユーザーのアイコンと名前を表示させるような場合です。<br>
<a href="http://photozou.jp/photo/show/784/19855636"><img src="http://art4.photozou.jp/pub/784/784/photo/19855636.png" alt="ユーザー選択画面例" width="413" height="310" style="border:0" /></a><br /><a href="http://photozou.jp/photo/show/784/19855636">ユーザー選択画面例</a> posted by <a href="http://photozou.jp/user/top/784">(C)フォト蔵</a>
</p>

<p>
動作は先程と変わらないはず......なのですが、IEの場合（IE6、IE7、IE8ともに）、
画像をクリックしてもチェックボックスの値が切り替わりません。
そこでブラウザーシェアの一番大きなIEで使い勝手が落ちてしまうのを防ぐべく、
次のようなJavaScriptを書きました。
</p>

<p>
<pre class="code"><code>&lt;input type="checkbox" id="foo" name="foo">
&lt;label for="foo" onclick="return clickFormLabel(this)">
  &lt;img src="bar.gif">
  キャプション
&lt;/label>

&lt;script type="text/javascript">
var clickFormLabel = function(label) {
  var e = null;
  try {
    e = document.getElementById(label.htmlFor);
  } catch (exception) {}
  if (e != null) {
    if (e.tagName == "INPUT") {
      switch (e.type) {
      case "checkbox":
        e.checked = !e.checked;
        break;
      case "radio":
        e.checked = true;
        break;
      default:
        e.focus();
        break;
      }
    } else {
      e.focus();
    }
  }
  return false;
};
&lt;/script>
</code></pre>
</p>

<p>
何をしているのかというと、label要素がクリックされた場合にfor属性で指定されたidの要素が
チェックボックスだったら値のON/OFFを切り替え、ラジオボタンだったら選択状態に。
それ以外の場合は要素にフォーカスを当てているだけです。
これでIEでも他のブラウザと同じような使い勝手を実現できます。
</p>

<p>
少しのJavaScriptでサイト訪問者の使い勝手が向上するケースは結構あるように思います。
そういう場合は積極的にJavaScriptを使っていきたいところですね。
</p>
]]>
        
    </content>
</entry>

<entry>
    <title>モテるエンジニアの7つの習慣</title>
    <link rel="alternate" type="text/html" href="http://labs.unoh.net/2009/04/7_1.html" />
    <link rel="service.edit" type="application/atom+xml" href="http://www.unoh.net/mt32/mt-atom.cgi/weblog/blog_id=2/entry_id=1613" title="モテるエンジニアの7つの習慣" />
    <id>tag:labs.unoh.net,2009://2.1613</id>
    
    <published>2009-04-01T08:05:44Z</published>
    <updated>2009-04-01T08:15:56Z</updated>
    
    <summary>Keitaです。 モテたいという気持ちは、たとえ既婚でも何歳でも変わらない気持ちのはず。 ウノウのwikiには、3年前につくられた「もてる」というページがあります。 そこで弊社のエンジニアが脈々と受け継いだモテ技術の一部を披露したいと思います。 詳細は以下から ...</summary>
    <author>
        <name>unoh</name>
        
    </author>
    
    <content type="html" xml:lang="ja" xml:base="http://labs.unoh.net/">
        <![CDATA[Keitaです。<br />
モテたいという気持ちは、たとえ既婚でも何歳でも変わらない気持ちのはず。<br />
ウノウのwikiには、3年前につくられた「もてる」というページがあります。<br />
そこで弊社のエンジニアが脈々と受け継いだモテ技術の一部を披露したいと思います。<br />
<br />
詳細は以下から<br />
]]>
        <![CDATA[
<h2>出会う箇所を増やす</h2>
最近ではSNSが流行らしいです。<br />
同じ趣味の相手が見つかったりするかもしれません。<br />
エンジニアだったら自分でSNS立てるといいかもしれませんね！<br />

<h2>チャックを閉める</h2>
社会の窓的なものはきちんと占めましょう。<br />
っていうか朝起きたときに鏡みて、寝癖と服装くらい確認しましょう。<br />
<br />
あけていることで新たな展開があることは残念ながらありません。<br />
<br />
<h2>髭や髪の毛はちゃんと整えましょう</h2>
こちらも常識ですね！<br />
<br />
<h2>フラグはきちんと立てましょう</h2>
「いつか一緒にいきましょう。」とかいってもいつかなんてきません。<br />
日時をきちんと決めましょう。もちろん社交辞令で言ってる場合もありますが、そこはあなたの魅力で何とかしてください。<br />
<br />
スケジュール管理はエンジニアの基本です。<br />
つかフラグとかいってる時点でアウトだよ。<br />
<br />
<h2>アナログな趣味を持つ</h2>
スポーツとかいいですね。（棒読み）<br />

<h2>相手のPCスキルに合わせた言葉を使える</h2>
重要です。相手に「デフォルト」とかいっても通じない場合があります。<br />
ましては「好きな関数何？」とか聞いても誰も答えてくれません。<br />
ちなみに僕はvar_dumpが好きです。<br />

<h2>相手の目を見て話す</h2>
目力、目力
<br />
<h2>来世に期待</h2>
明日はきっといいことあるさ<br />

<h2>焼肉</h2>
焼肉が食べたいです<br />

<br />
<br />
さて、該当ページは僕がもてるために作られたページですが、いまだに実行できていません。<br />
そろそろチャックは閉めておきたいと思います。<br />
<br />
皆様もモテエンジニアを目指してはいかがでしょうか<br />
<br />
参考:<br />
<a href="http://labs.unoh.net/2009/03/post_133.html">デキるテスターの七つの習慣</a><br />

<a href="http://labs.unoh.net/2006/11/7.html">女性に愛されるプログラマーの7つの要素</a><br />
<a href="http://b.hatena.ne.jp/t/%E3%81%8A%E5%89%8D%E3%81%8C%E8%A8%80%E3%81%86%E3%81%AA">お前が言うな</a>]]>
    </content>
</entry>

<entry>
    <title>デキるテスターの七つの習慣</title>
    <link rel="alternate" type="text/html" href="http://labs.unoh.net/2009/03/post_133.html" />
    <link rel="service.edit" type="application/atom+xml" href="http://www.unoh.net/mt32/mt-atom.cgi/weblog/blog_id=2/entry_id=1611" title="デキるテスターの七つの習慣" />
    <id>tag:labs.unoh.net,2009://2.1611</id>
    
    <published>2009-03-27T08:16:16Z</published>
    <updated>2009-03-27T08:30:21Z</updated>
    
    <summary>こんにちは！やまもと＠テスト番長です。 今回はなんだか怪しいタイトルになってしまいましたが、 テスターの心構えについての良い記事がありましたのでご紹介します。 The Seven Habits of Highly Effective Testers - By Steve Miller 以下、まとめ（超意訳）です。 ぜひ原文もお読みになってください。 1. 前向きであれ プロジェクトが失敗した時、それが他の人や事情のせいだったと考えても得るものはありません。 失敗から得た教訓を次に繋げるべきです。 	要件定義に参加するようにしましょう。 	漏れがないかトレーサビリティを分析しましょう。 	テストの効果をメンバーに伝えましょう。 	不具合を分かりやすく報告しましょう。 2. ゴールを念頭に置く 周囲と相談の上、前もってゴールとなる品質基準を決めておくべきです。 評価基準を満たしたかどうかを客観...</summary>
    <author>
        <name>unoh</name>
        
    </author>
    
        <category term="Tips" />
    
    <content type="html" xml:lang="ja" xml:base="http://labs.unoh.net/">
        <![CDATA[こんにちは！やまもと＠テスト番長です。

今回はなんだか怪しいタイトルになってしまいましたが、
テスターの心構えについての良い記事がありましたのでご紹介します。

<a href="http://www.stickyminds.com/getfile.asp?ot=XML&id=14805&fn=XUS223431782file2%2Epdf">The Seven Habits of Highly Effective Testers </a>- By Steve Miller

以下、まとめ（超意訳）です。
ぜひ原文もお読みになってください。
<blockquote>
<b>1. 前向きであれ</b>
プロジェクトが失敗した時、それが他の人や事情のせいだったと考えても得るものはありません。
失敗から得た教訓を次に繋げるべきです。

<ul>
	<li>要件定義に参加するようにしましょう。</li>
	<li>漏れがないかトレーサビリティを分析しましょう。</li>
	<li>テストの効果をメンバーに伝えましょう。</li>
	<li>不具合を分かりやすく報告しましょう。</li>
</ul>

<b>2. ゴールを念頭に置く</b>
周囲と相談の上、前もってゴールとなる品質基準を決めておくべきです。
評価基準を満たしたかどうかを客観的に評価できます。

<b>3. 大事なことから先にやる</b>
重要な機能が正常に動作することをまず確認しましょう。
オーバーフローやインジェクションなどのテストも重要ですが、後で行えばいいことです。

<b>4. 開発チームと良い関係を作る</b>
開発とテストチームがお互いに非難しあうようになってはいけません。
良い協力関係を作りましょう。

<ul>
	<li>ナレッジを共有しましょう。</li>
	<li>異なった役割の人と一緒にランチを取って交流しましょう。</li>
	<li>良い仕事をした人はちゃんと褒めましょう。</li>
	<li>困っているメンバーがいたら助けましょう。</li>
</ul>

<b>5. 他人の話を良く聞く</b>
問題を性急に判断してしまわずに、まず良く理解することを心がけましょう。
異なる考え方を攻撃せず、経験を元により良いアプローチについて説明しましょう。

<b>6. 仲間と協力し合う</b>
共同作業は協調性が重要です。
メンバーは異なる能力を持っているので、互いに長所を生かしましょう。
コミュニケーションを取り合いましょう。


<b>7. 刃を研ぎ澄ます</b>
新しい技術を習得しスキルを磨くことを心がけましょう。テスト本を漁りましょう。
趣味や余暇も充実させましょう。
</blockquote>


色々思い当たるところがありますね。
自分ももっと精進しようと思いました。
]]>
        
    </content>
</entry>

</feed> 

