バリケンのRuby日記 RSSフィード

2006-06-30

[] QuickMLのソースコードを読む(20) quickml/sweeper.rbの続き  QuickMLのソースコードを読む(20) quickml/sweeper.rbの続き - バリケンのRuby日記 を含むブックマーク はてなブックマーク -  QuickMLのソースコードを読む(20) quickml/sweeper.rbの続き - バリケンのRuby日記  QuickMLのソースコードを読む(20) quickml/sweeper.rbの続き - バリケンのRuby日記 のブックマークコメント

昨日の日記の続きだよ。

    public
    def start
      @logger.vlog "Sweeper started"
      loop do
	sleep(@config.sweep_interval)
	begin
	  sweep
	rescue Exception => e
	  @logger.log "Unknown Sweep Error: #{e.class}: #{e.message}"
	  @logger.log e.backtrace
	end
      end
    end

これはQuickML::Sweeper#startメソッドだね。

まずスイーパーの起動をQuickML::Logger#vlogメソッドでログに記録し、Kernel#loopメソッドでループを開始、Config#sweep_intervalメソッドで、Configオブジェクトに定義されているsweep_intervalの時間だけスイープのインターバルをとり、sweepメソッドを実行、Exception例外が発生した場合はその例外オブジェクトを得て、エラーメッセージとバックトレース情報をQuickML::Logger.logメソッドでログに記録しているよ。

今日はここまで。

トラックバック - http://rubyist.g.hatena.ne.jp/muscovyduck/20060630

2006-06-29

[] QuickMLソースコードを読む(19) quickml/sweeper.rbの続き  QuickMLのソースコードを読む(19) quickml/sweeper.rbの続き - バリケンのRuby日記 を含むブックマーク はてなブックマーク -  QuickMLのソースコードを読む(19) quickml/sweeper.rbの続き - バリケンのRuby日記  QuickMLのソースコードを読む(19) quickml/sweeper.rbの続き - バリケンのRuby日記 のブックマークコメント

昨日の日記の続きだよ。

    def sweep
      @status = :sweeping
      @logger.vlog "Sweeper runs"
      Dir.new(@config.data_dir).each {|filename|
        filename = File.join(@config.data_dir, filename)
        if ml_file?(filename)
          address = mladdress(mlname(filename))
          @config.ml_mutex(address).synchronize {
            ml = QuickML.new(@config, address)
            ml.write_ml_config unless ml.ml_config_exist?
            sweep_ml(ml)
          }
        end
      }
      @logger.vlog "Sweeper finished"
      @status = :safe
    end

これはQuickML::Sweeperクラスプライベートメソッド、sweepメソッドだね。

まずシンボルオブジェクト「:sweeping」に、インスタンス変数@statusを貼り付けるよ。次にQuickML::Logger#vlogメソッド(これはquickml/logger.rbで定義されているよ)で、ログファイルにスイーパーが起動中であることを記録しているよ。

次にDirクラスインスタンス(指定したディレクトリ内の各要素を表す)を@config.data_dirを引数として生成し、その各要素に対してコードブロックを実行するよ。コードブロックの内容は、まずファイル名をフルパス表現にしたあと、ml_file?メソッドでそのファイルQuickMLで扱うファイル名であるかどうかをチェックして、そうであるならMLメンバーメールアドレス一覧をファイルから読んで、Mutex#synchronizeによってスレッドロックしたあとにQuickMLクラスインスタンスを生成、そのメーリングリスト用のconfigファイルがないのであれば生成して、メーリングリストのスイープを実行するよ。

最後にスイーパーの終了をログに記録して、シンボルオブジェクト:safeにインスタンス変数@statusを貼り付けるよ。

今日はここまで。

トラックバック - http://rubyist.g.hatena.ne.jp/muscovyduck/20060629

2006-06-28

[] QuickMLソースコードを読む(18) quickml/sweeper.rbの続き  QuickMLのソースコードを読む(18) quickml/sweeper.rbの続き - バリケンのRuby日記 を含むブックマーク はてなブックマーク -  QuickMLのソースコードを読む(18) quickml/sweeper.rbの続き - バリケンのRuby日記  QuickMLのソースコードを読む(18) quickml/sweeper.rbの続き - バリケンのRuby日記 のブックマークコメント

昨日の日記の続きだよ。

    def sweep_ml (ml)
      if ml.inactive?
        @logger.log "[#{ml.name}]: Inactive"
        ml.close
      elsif ml.need_alert?
        ml.report_ml_close_soon
      end
    end

これはQuickML::Sweeperクラスプライベートメソッド、sweep_mlメソッドだね。

まずml変数が貼り付けてあるオブジェクトに対して、QuickML::QuickML#inactive?メソッド(これはquickml/core.rbで定義されているよ)で、アクティブではないメーリングリストかどうか(ずっと投稿がないか)をチェックしているよ。

次にQuickML::Logger#logメソッド(これはquickml/logger.rbで定義されているよ)で、ログファイルにそのメーリングリストアクティブではないことを記録しているよ。

そしてQuickML::QuickML#closeメソッド(これはquickml/core.rbで定義されているよ)で、メーリングリスト削除をするよ。

elsif以下は、メーリングリストアクティブな場合だね。今度はQuickML::QuickML#need_alert?メソッド(これはquickml/core.rbで定義されているよ)で、アラートを送信する必要があるかをチェックするよ。QuickMLでは一定期間投稿がないとメーリングリストが自動的に削除される機能がある(削除される日の直前に「投稿がないともうすぐ消去されてしまいます」というアラートを送る)んだよ。

アラートを送る必要があるなら、QuickML::QuickML#report_ml_close_soonメソッド(これはquickml/core.rbで定義されているよ)で、メーリングリストメンバー全員にアラートメールを送信するよ。

今日はここまで。

トラックバック - http://rubyist.g.hatena.ne.jp/muscovyduck/20060628

2006-06-27

[] QuickMLソースコードを読む(17) quickml/sweeper.rbの続き  QuickMLのソースコードを読む(17) quickml/sweeper.rbの続き - バリケンのRuby日記 を含むブックマーク はてなブックマーク -  QuickMLのソースコードを読む(17) quickml/sweeper.rbの続き - バリケンのRuby日記  QuickMLのソースコードを読む(17) quickml/sweeper.rbの続き - バリケンのRuby日記 のブックマークコメント

昨日の日記の続きだよ。

    def ml_file? (filename)
      File.file?(filename) && QuickML.valid_name?(mlname(filename))
    end

これはQuickML::Sweeperのプライベートメソッド、ml_file?メソッドだね。

まずfilename変数が貼り付けてあるオブジェクトに対して、mlnameメソッドでフルパスファイル名からファイル名のみを取り出すよ。

次にQuickML::QuickML::valid_name?メソッド(これはquickml/core.rbで定義されているよ)で、QuickMLで扱うことのできる正しいファイル名かどうかをチェックしているよ。

そしてFile::file?メソッドで「そのファイル存在するか(かつ、ディレクトリじゃなくて本当に「ファイル」か)」をチェックするよ。

そして「&&」は「かつ」だったね。だから、「そのファイル存在し、かつQuickMLで扱うことのできる正しいファイル名である」ならtrueを、そうでなければfalseを返すメソッド、ということになるね。

今日はここまで。

トラックバック - http://rubyist.g.hatena.ne.jp/muscovyduck/20060627

2006-06-26

[] QuickMLソースコードを読む(16) quickml/sweeper.rbの続き  QuickMLのソースコードを読む(16) quickml/sweeper.rbの続き - バリケンのRuby日記 を含むブックマーク はてなブックマーク -  QuickMLのソースコードを読む(16) quickml/sweeper.rbの続き - バリケンのRuby日記  QuickMLのソースコードを読む(16) quickml/sweeper.rbの続き - バリケンのRuby日記 のブックマークコメント

昨日の日記の続きだよ。

まずはプライベートメソッドから見ていくね。

    private
    def mlname (filename)
      File.basename(filename)
    end

これはQuickML::Sweeperのプライベートメソッド、mlnameメソッドだね。File::basenameメソッドを使って、フルパスファイル名からファイル名のみを取り出すよ。

    def mladdress (name)
      address = name
      address += if name.include?("@") then "." else "@" end
      address += @config.domain
    end

これはQuickML::Sweeperのプライベートメソッド、mladdressメソッドだね。まず引数で与えられたオブジェクトにaddressというラベルを貼り付け、次にそのオブジェクトが「@」を含んでいれば「.」を、含んでいないのであれば「@」を文字列の最後に付加した文字列オブジェクトを生成し、addressというラベルを貼りなおすよ。最後にインスタンス変数@configからQuickML::Config#domainメソッド(アクセサ)で得られた文字列を、addressが指すオブジェクトの最後に付加した文字列オブジェクトを生成し、addressというラベルを貼りなおすよ。

今日はここまで。

トラックバック - http://rubyist.g.hatena.ne.jp/muscovyduck/20060626

2006-06-25

[] QuickMLのソースコードを読む(15) quickml/sweeper.rb  QuickMLのソースコードを読む(15) quickml/sweeper.rb - バリケンのRuby日記 を含むブックマーク はてなブックマーク -  QuickMLのソースコードを読む(15) quickml/sweeper.rb - バリケンのRuby日記  QuickMLのソースコードを読む(15) quickml/sweeper.rb - バリケンのRuby日記 のブックマークコメント

今日からは、/usr/lib/ruby/1.8/quickml/sweeper.rbを読んでいくよ。

#
# quickml/sweeper - a part of quickml server
#
# Copyright (C) 2002-2004 Satoru Takabayashi <satoru@namazu.org>
#     All rights reserved.
#     This is free software with ABSOLUTELY NO WARRANTY.
#
# You can redistribute it and/or modify it under the terms of
# the GNU General Public License version 2.
#

module QuickML
  class Sweeper
    def initialize (config)
      @config = config
      @status = :safe
      @logger = @config.logger
    end

    private
    def mlname (filename)
      File.basename(filename)
    end

    def mladdress (name)
      address = name
      address += if name.include?("@") then "." else "@" end
      address += @config.domain
    end

    def ml_file? (filename)
      File.file?(filename) && QuickML.valid_name?(mlname(filename))
    end

    def sweep_ml (ml)
      if ml.inactive?
        @logger.log "[#{ml.name}]: Inactive"
        ml.close
      elsif ml.need_alert?
        ml.report_ml_close_soon
      end
    end

    def sweep
      @status = :sweeping
      @logger.vlog "Sweeper runs"
      Dir.new(@config.data_dir).each {|filename|
        filename = File.join(@config.data_dir, filename)
        if ml_file?(filename)
          address = mladdress(mlname(filename))
          @config.ml_mutex(address).synchronize {
            ml = QuickML.new(@config, address)
            ml.write_ml_config unless ml.ml_config_exist?
            sweep_ml(ml)
          }
        end
      }
      @logger.vlog "Sweeper finished"
      @status = :safe
    end

    public
    def start
      @logger.vlog "Sweeper started"
      loop do
        sleep(@config.sweep_interval)
        begin
          sweep
        rescue Exception => e
          @logger.log "Unknown Sweep Error: #{e.class}: #{e.message}"
          @logger.log e.backtrace
        end
      end
    end

    def shutdown
      until @status == :safe
        sleep(0.5)
      end
      @logger.vlog "Sweeper shutdown"
    end
  end
end

じゃあ、最初から。

module QuickML
  class Sweeper
    def initialize (config)
      @config = config
      @status = :safe
      @logger = @config.logger
    end

これはQuickML::Sweeperクラスからインスタンスを生成する時に呼び出されるinitializeメソッドだね。まずconfigで与えられたオブジェクトに、インスタンス変数@configというラベルを貼り付けているよ。次に、シンボルオブジェクト:safeにはインスタンス変数@statusを、QuickML::Config#loggerメソッド(アクセサ)でQuickML::Loggerクラスのインスタンスを得てインスタンス変数@loggerというラベルを貼り付けているよ。

とりあえず今日はここまで。

トラックバック - http://rubyist.g.hatena.ne.jp/muscovyduck/20060625

2006-06-24

[] coLinuxを複数同時に起動して相互に通信する  coLinuxを複数同時に起動して相互に通信する - バリケンのRuby日記 を含むブックマーク はてなブックマーク -  coLinuxを複数同時に起動して相互に通信する - バリケンのRuby日記  coLinuxを複数同時に起動して相互に通信する - バリケンのRuby日記 のブックマークコメント

昨日の日記インストール手順をメモした「coLinux」の話題を続けるよ。

Ruby on RailsデプロイツールCapistrano(旧名SwitchTower)を評価しようと思うと、開発用と本番用の合計2台のLinuxマシンが必要になるよ。でも、coLinuxを同時に2つ起動すれば、1台のマシンで評価することができるよ。

以下、簡単な手順だよ。

TAP-Win32 Adapterの追加

コントロールパネル」「ハードウェアの追加」で「ネットワークアダプタ」「TapWin32Project」「TAP-Win32 Adapter」を選択して、「TAP-Win32 Adapter」をもうひとつ追加するよ。

アダプタの名前の変更

二つあるTAP-Win32 Adapterの名前を、それぞれ「tap0」「tap1」に変更するよ。

ファイルコピー

debian.bat」「debian.conf」「swap_device」「Debian-3.0r2.ext3-mit-backports.1gb」を、それぞれ「debian2.bat」「debian2.conf」「swap_device2」「Debian-3.0r2.ext3-mit-backports.1gb.2」としてコピーするよ。

debian.batの編集

次のようにするよ。

colinux-daemon @debian.conf -t nt

debian.confの編集

次のようにするよ。

kernel=vmlinux
cobd0=Debian-3.0r2.ext3-mit-backports.1gb
cobd1=swap_device
initrd=initrd.gz
mem=64
eth0=tuntap,tap0,00:FF:8A:7E:AF:00
root=/dev/cobd/0
cofs0=C:\

debian2.batの編集

次のようにするよ。

colinux-daemon @debian2.conf -t nt

debian2.confの編集

次のようにするよ。

kernel=vmlinux
cobd0=Debian-3.0r2.ext3-mit-backports.1gb.2
cobd1=swap_device2
initrd=initrd.gz
mem=64
eth0=tuntap,tap1,00:FF:8A:7E:AF:01
root=/dev/cobd/0
cofs0=C:\

インターネット接続の共有を解除

インターネット接続の共有」を解除するよ。

ブリッジの設定

TAP-Win32 Adapter「tap0」「tap1」の二つのインタフェースを、ブリッジに設定するよ。

インターネット接続の共有の再設定

インターネット接続の共有」を設定するよ。共有に指定するインタフェースは、先ほど「tap0」「tap1」の二つのインタフェースを束ねて作られたブリッジインタフェースにするよ。

ブリッジインタフェースのIPアドレスの設定

ブリッジインタフェースのIPアドレスを192.168.5.1に変更するよ。

coLinuxのIPアドレスの設定

debian2.batをダブルクリックして起動して、IPアドレスを192.168.5.41に変更するよ。

もうひとつのcoLinuxの起動

debian.batをダブルクリックして起動して、同時に2つのcoLinuxが起動できることを確認してね。

また、pingコマンドとかで双方のLinuxとが通信できることを確認してね。

サービス化

もちろん2つのcoLinuxを別々のWindowsサービスとして登録することができるよ。いったん以前の設定を削除して、名前を変えて二つ登録してね。

> cd C:\Program Files\coLinux
> colinux-daemon --remove-service
> colinux-daemon kernel="C:\Program Files\coLinux\vmlinux" cobd0="D:\coLinux\debian\Debian-3.0r2.ext3-mit-backports.1gb" cobd1="D:\coLinux\debian\swap_device" initrd="C:\Program Files\coLinux\initrd.gz" mem=64 eth0=tuntap,tap0,00:FF:8A:7E:AF:01 root=/dev/cobd/0 --install-service "coLinux Debian 1"
> colinux-daemon kernel="C:\Program Files\coLinux\vmlinux" cobd0="D:\coLinux\debian\Debian-3.0r2.ext3-mit-backports.1gb.2" cobd1="D:\coLinux\debian\swap_device2" initrd="C:\Program Files\coLinux\initrd.gz" mem=64 eth0=tuntap,tap1,00:FF:8A:7E:AF:02 root=/dev/cobd/0 --install-service "coLinux Debian 2"

もしサービスから削除したくなったら、--remove-serviceに名前を指定してね。

> cd C:\Program Files\coLinux
> colinux-daemon --remove-service "coLinux Debian 1"
> colinux-daemon --remove-service "coLinux Debian 2"

参考

2006-06-23

[] coLinuxインストールメモ  coLinuxインストールメモ - バリケンのRuby日記 を含むブックマーク はてなブックマーク -  coLinuxインストールメモ - バリケンのRuby日記  coLinuxインストールメモ - バリケンのRuby日記 のブックマークコメント

Ruby on RailsWebアプリケーションを開発するなら、Windows上でLinuxを同時に利用できるようにするソフトウェアcoLinux」を利用すると便利だよ。

coLinuxは、Windows上で動く「Linuxエミュレータ」だよ。「仮想的なネットワークでつながっている仮想的なLinuxマシン」が、もう一台自分のPCの中に作られるイメージなので、次のような利点があるよ。

ということで、ぼくがcoLinuxを導入したときのメモを公開してみることにするよ。

ダウンロード

インストール

ファイルシステムの展開

debian.batをテキストエディタで作成(coLinuxインストールフォルダに)

coLinuxインストールしたフォルダ(C:\Program Files\coLinux)に、debian.batというファイルを作成するよ。

ファイルの中身は一行だけ。次のようにしてね。

colinux-daemon @debian.conf

debian.confをテキストエディタで作成(coLinuxインストールフォルダに)

coLinuxインストールしたフォルダ(C:\Program Files\coLinux)に、debian.confというファイルを作成するよ。

ファイルの中身は次のようにしてね。

kernel=vmlinux
cobd0=Debian-3.0r2.ext3-mit-backports.1gb
cobd1=swap_device
initrd=initrd.gz
mem=64
eth0=tuntap
root=/dev/cobd/0
cofs0=C:\

swap用の空のファイルの作成

Windowsコマンドプロンプトを起動して、次のコマンドを実行するよ。

> fsutil file createnew swap_device 268697600

そうすると「swap_device」というファイルができあがるから、coLinuxインストールしたフォルダ(C:\Program Files\coLinux)に移動してね。

coLinuxを起動してログイン

さあ、いよいよcoLinuxを起動してみよう。debian.batをダブルクリックしてみてね。coLinuxのコンソールが起動して、最後に「login:」と表示されたら起動成功だよ。ユーザー名はrootパスワードrootログインしてね。

テキストエディタのありか

Linuxで設定ファイルを操作するには普通は「vi」というテキストエディタを使うんだけど、なぜかviエディタの場所が「/initrd/bin/vi」という変なところにあるから気をつけてね。

/etc/network/interfacesを修正(現在ネットワークとぶつからないように)

ネットワークの設定を必要に応じて修正してね。初期値は、192.168.0.40となっているよ。テキストエディタネットワークの設定ファイルを修正するよ。

# /initrd/bin/vi /etc/network/interfaces

ぼくはIPアドレスを192.168.5.40に、デフォルトゲートウェイを192.168.5.1に変更してみたよ。変更するには、addressとgatewayの部分を次のように変更するよ。

   address 192.168.5.40
   netmask 255.255.255.0
   gateway 192.168.5.1

/etc/resolv.confを修正

次はDNSサーバの設定を変更するよ。今回はWindowsDNSの設定をリレーして利用するから、デフォルトゲートウェイと同じIPアドレス(192.168.5.1)にするよ。

# /initrd/bin/vi /etc/resolv.conf

次のような内容にするよ。

nameserver 192.168.5.1

swapを初期化し、有効にする

スワップファイルを有効にするため、次のコマンドを実行するよ。

# mkswap /dev/cobd1
# swapon /dev/cobd1

/etc/fstabを編集swapの追加)

次に起動したときに自動的にswapを有効にするための設定をするよ。

# /initrd/bin/vi /etc/fstab

ファイルの最後に、次のような内容を追加するよ。

/dev/cobd1      none            swap    sw                      0       0

Windows側のネットワークの設定

ここまできたら、Windows側のネットワークの設定をしてcoLinuxと通信できるようにしよう。

/etc/apt/sources.list編集

アップデートパッケージの入手先を設定する「/etc/apt/sources.listファイルを設定するよ。

# /initrd/bin/vi /etc/apt/sources.list

次のように設定してね。

deb http://www.t.ring.gr.jp/pub/linux/debian/debian sarge main contrib non-free
deb http://security.debian.org/ sarge/updates main contrib non-free

この設定でうまくいかないときは、他のサーバを直接指定してみてね。たとえばasahi-netサーバなら、次のようになるよ。

deb http://ring.asahi-net.or.jp/archives/linux/debian/debian sarge main contrib non-free
deb http://security.debian.org/ sarge/updates main contrib non-free

proxyの設定

会社とか学校とかでインターネットに直接接続できない人は、proxyの設定をしてね。

# export http_proxy=http://proxyサーバのホスト名:ポート番号/

ネットワークの設定を有効にする

次のコマンドネットワークの設定を有効にするよ。

# /etc/init.d/networking restart

sargeへアップグレード

次のコマンドで、最新のバージョンにアップグレードするよ。

# apt-get update
# apt-get dist-upgrade

インストールの途中でいくつかダイアログが出るけど、特にこだわりがなければ「YesかNoか聞かれてもすべてデフォルト(リターンキーを押す)」、「What interface should be used for configuring packages?」には「Dialog」、「See only questions that are of what priority and higher?」には「critical」を選べば問題ないと思うよ。

ちなみにこの「apt-getコマンドは旧バージョンコマンドだから、アップグレード後は「aptitude」コマンドを使うようにしてね。使い方はapt-getもaptitudeもだいたい同じだよ。

カーネルモジュールインストール

起動時に読み込まれるカーネルモジュールインストールするよ。

# mount -o ro -t cofs /dev/cofs0 /mnt
# cp /mnt/Program\ Files/coLinux/vmlinux-modules.tar.gz /root
# cd /
# tar xvzf /root/vmlinux-modules.tar.gz
# rm  /root/vmlinux-modules.tar.gz
# umount /mnt

タイムゾーンの設定

時計日本時間に合わせよう。

# tzconfig

最初の質問には「y」、次の質問には「5」(Asia)、最後の質問には「Tokyo」を入力してね。

日本語キーボードの設定

日本語キーボードを利用している人は、日本語キーボードの設定が必要だよ。次のコマンドを入力してね。

# dpkg-reconfigure console-data

最初のダイアログで「OK」が出たらそのまま改行キーを押してね。

次のダイアログではカーソルキーで「Select keymap from arch list」を選択して、タブキーで「Ok」のところにカーソルを移動してから改行キーを押してね。

その次のダイアログではカーソルキーで「qwerty」を選択して、タブキーで「Ok」のところにカーソルを移動してから改行キーを押してね。

その次のダイアログではカーソルキーで「Japanese」を選択して、タブキーで「Ok」のところにカーソルを移動してから改行キーを押してね。

その次のダイアログではカーソルキーで「Standard」を選択して、タブキーで「Ok」のところにカーソルを移動してから改行キーを押してね。

これで日本語キーボードの設定は完了だよ。

日本語対応のターミナルを使おう

coLinux標準のターミナルでは、日本語が表示できないよ。coLinuxにsshdをインストールして、通常の作業は日本語表示のできるPuTTYとかの、Windows上で動く日本語表示のできるターミナルソフトを利用すると、何かと便利だよ。

coLinuxでsshdをインストールするには、次のようにするよ。

# export http_proxy=http://proxyサーバのホスト名:ポート番号/
# aptitude install ssh

Windowsサービスとして自動起動するには

coLinuxは、Windowsサービスとして自動起動させることもできるよ。サービスとして登録するには、ちょっと長いけど次のコマンドを実行してね。

> cd C:\Program Files\coLinux
> colinux-daemon kernel="C:\Program Files\coLinux\vmlinux" cobd0="C:\Program Files\coLinux\Debian-3.0r2.ext3-mit-backports.1gb" cobd1="C:\Program Files\coLinux\swap_device" initrd="C:\Program Files\coLinux\initrd.gz" mem=64 eth0=tuntap root=/dev/cobd/0 cofs0=C:\ --install-service

自動起動させるには、コントロールパネルの「管理ツール」「サービス」の「Cooperative Linux」を、スタートアップの種類を「自動」に設定してね。

サービスとして起動すると、coLinuxのコンソールが表示されないよ。coLinuxアクセスするには、Windows上で動くターミナルソフトを利用してアクセスしてね。

サービスから削除したくなったら、次のコマンドを実行してね。

> cd C:\Program Files\coLinux
> colinux-daemon --remove-service

2006-06-22

[][][] Subversionの練習  Subversionの練習 - バリケンのRuby日記 を含むブックマーク はてなブックマーク -  Subversionの練習 - バリケンのRuby日記  Subversionの練習 - バリケンのRuby日記 のブックマークコメント

今日は久しぶりにRuby on Railsに戻ってみるよ。

Ruby on Railsで開発するときにはSubversionというバージョン管理ツールを使うといいみたいだから、Subversionの使い方を練習してみることにするよ。

まずは、Subversionインストール

# aptitude install subversion

もちろんRails自体のインストールも必要だよ。ちなみにDebian系のLinuxディストリビューションRailsインストールする手順はこちらがよくまとまっていると思うよ。

じゃあ、以前の日記でもネタにしたTinyURLでやってみることにするよ。

作業ディレクトリへの移動

$ cd Rails

プロジェクトの開始

$ rails Tinyurl
$ cd Tinyurl

subversionレポジトリの作成

$ mkdir ~/subversion
$ svnadmin create --fs-type fsfs ~/subversion/rails.tinyurl

レポジトリインポートする

$ svn import . file:///home/ユーザー名/subversion/rails.tinyurl

エディタが立ち上がるので、「create project」と編集して保存するよ。

いったん削除して、チェックアウトしなおす

$ cd ..
$ rm -rf Tinyurl
$ svn co file:///home/ユーザー名/subversion/rails.tinyurl Tinyurl
$ cd Tinyurl

database.ymlの移動

$ svn move config/database.yml config/database.example.yml
$ svn ci -m 'move database.yml'
$ svn update

database.ymlを無視ファイルに指定

database.ymlは動作環境によって異なるから、subversionの管理下から除外するよ。

$ svn propset svn:ignore "database.yml" config/
$ svn ci -m 'add ignore config/database.yml'
$ svn update

ログファイル削除し、無視ファイルに指定

ログファイルは動作環境によって異なるから、subversionの管理下から除外するよ。

$ svn remove log/*
$ svn ci -m 'rm logfile'
$ svn update
$ svn propset svn:ignore "*.log" log/
$ svn ci -m 'add ignore ./log/*.log'
$ svn update

config/database.ymlのコピー

$ cp config/database.example.yml config/database.yml

config/database.ymlの編集

$ vi config/database.yml

編集内容はデータベースによって異なるよ。

データベースの作成

$ vi db/create_database.sql

次のような内容とするよ。

CREATE DATABASE Tinyurl_development CHARACTER SET utf8;
CREATE DATABASE Tinyurl_test CHARACTER SET utf8;
CREATE DATABASE Tinyurl_production CHARACTER SET utf8;

作成したファイルsubversionの管理下に置くよ。

$ svn add db/create_database.sql
$ svn ci -m 'edit db/create_database.sql'
$ svn update

データベースを作成するよ。

$ mysql -uroot -p < db/create_database.sql

このコマンドも、データベースによって異なるよ。


モデルの生成

$ ruby script/generate model Map

生成したファイルsubversionの管理下に置くよ。

$ svn add app/models/map.rb
$ svn add test/unit/map_test.rb
$ svn add test/fixtures/maps.yml
$ svn add db/migrate/
$ svn ci -m 'create app/models/map.rb'
$ svn update

テーブルの作成

$ vi db/migrate/001_create_maps.rb

次のように編集するよ。

class CreateMaps < ActiveRecord::Migration
  def self.up
    create_table :maps do |t|
      t.column :token, :string
      t.column :url, :string
      t.column :created_on, :time
    end
  end

  def self.down
    drop_table :maps
  end
end

チェックインするよ。

$ svn ci -m 'edit db/migrate/001_create_maps.rb'
$ svn update

テーブルを作成するよ。

$ rake migrate

モデル編集(app/models/map.rb)

$ vi app/models/map.rb

次のように編集するよ。

class Map < ActiveRecord::Base
  validates_presence_of :url
  validates_format_of :url, :with =>/:\/\//

  def before_create
    self.token = Map.generate_token(5)
  end

  def tinyurl
    ['http://localhost:3000', self.token].join '/'
  end

  private
  def self.generate_token(i=5)
    chars = ['a'..'z', 'A'..'Z', '0'..'9'].map {|r|
      r.to_a
    }.flatten
    token = ''
    i.times do
      token << chars[rand(chars.length)]
    end
    return token
  end
end

チェックインするよ。

$ svn ci -m 'edit app/models/map.rb'
$ svn update

コントローラーの生成

$ ruby script/generate controller Map index create redirect

生成されたファイルsubversionの管理下に置くよ。

$ svn add app/views/map
$ svn add app/controllers/map_controller.rb
$ svn add test/functional/map_controller_test.rb
$ svn add app/helpers/map_helper.rb
$ svn ci -m 'create app/controllers/map_controller.rb'
$ svn update

コントローラー編集(app/controllers/map_controller.rb)

$ vi app/controllers/map_controller.rb

次のように編集するよ。

class MapController < ApplicationController

  def index
    @map = Map.new
  end

  def create
    @map = Map.find_by_url(params[:map][:url])
    unless @map
      @map = Map.new(params[:map])
      unless @map.save
        render :action => 'index'
      end
    end
  end

  def redirect
    map = Map.find_by_token(params[:token])
    redirect_to(map.url) if map
  end
end

チェックインするよ。

 $ svn ci -m 'edit app/controllers/map_controller.rb'
 $ svn update

ルータの設定(config/routes.rbの編集)

$ vi config/routes.rb

Add your own custom routes here.」の下あたりに次を追加するよ。

  map.connect ':token', :token => /\w{5}/,
    :controller => 'map', :action => 'redirect'

チェックインするよ。

$ svn ci -m 'edit config/routes.rb'
$ svn update

ビューの編集(app/views/map/_form.rhtml)

$ vi app/views/map/_form.rhtml

次のように編集するよ。

<%= form_tag :action => 'create' %>
  <%= text_field("map", "url", :size => 40) %>
  <%= submit_tag("TinyURL!") %>
<%= end_form_tag %>

作成したファイルsubversionの管理下に置くよ。

$ svn add app/views/map/_form.rhtml
$ svn ci -m 'create app/views/map/_form.rhtml'
$ svn update

ビューの編集(app/views/map/index.rhtml)

$ vi app/views/map/index.rhtml

次のように編集するよ。

<%= render :partial => 'form' %>

チェックインするよ。

$ svn ci -m 'edit app/views/map/index.rhtml'
$ svn update

ビューの編集(app/views/map/create.rhtml)

$ vi app/views/map/create.rhtml

<p>The following URL:</p>
<p><%=h @map.url %></p>
<p>has a length of <%= @map.url.size %> characters and
resulted in the following TinyURL which has a length of
<%=h @map.tinyurl.size %> characters:</p>
<p><%=h @map.tinyurl %></p>

<%= render :partial => 'form' %>

チェックインするよ。

$ svn ci -m 'edit app/views/map/create.rhtml'
$ svn update

レイアウト(ビュー)の編集(app/views/layouts/map.rhtml)

$ vi app/views/layouts/map.rhtml

次のように編集するよ。

<html>
<head>
<title>TinyURL on Rails</title>
</head>

<body>
<h1>TinyURL!</h1>

<%= error_messages_for('map') %>
<p style="color: green"><%= flash[:notice] %></p>

<%= @content_for_layout %>

</body>
</html>

作成したファイルsubversionの管理下に置くよ。

$ svn add app/views/layouts/map.rhtml
$ svn ci -m 'create app/views/layouts/map.rhtml'
$ svn update

テストWebサーバの起動

$ ruby script/server

ブラウザアクセスして動作確認

http://localhost:3000/map/index

うーん、けっこう面倒だねえ。頻繁にチェックインしすぎかなあ?

トラックバック - http://rubyist.g.hatena.ne.jp/muscovyduck/20060622

2006-06-21

[] QuickMLソースコードを読む(14) /usr/lib/ruby/1.8/quickml/utils.rbの続き  QuickMLのソースコードを読む(14) /usr/lib/ruby/1.8/quickml/utils.rbの続き - バリケンのRuby日記 を含むブックマーク はてなブックマーク -  QuickMLのソースコードを読む(14) /usr/lib/ruby/1.8/quickml/utils.rbの続き - バリケンのRuby日記  QuickMLのソースコードを読む(14) /usr/lib/ruby/1.8/quickml/utils.rbの続き - バリケンのRuby日記 のブックマークコメント

昨日の日記の続きだよ。

Integer#commifyメソッド

class Integer
  # commify(12345) => "12,345"
  def commify
    numstr = self.to_s
    true while numstr.sub!(/^([-+]?\d+)(\d{3})/, '\1,\2')
    return numstr
  end
end

これは、Integer#commifyメソッドの定義だね。整数を3桁ごとにカンマで区切った文字列を返すよ。

まず、Integer#to_sメソッドで整数から文字列オブジェクトを生成して、numstrというラベルを貼るよ。

次に、正規表現/^([-+]?\d+)(\d{3})/つまり「文字列の先頭の次にプラス記号またはマイナス記号のゼロまたは1回の繰り返しのあとに、数字1文字の1回以上の繰り返し」と「数字3文字」がそれぞれ「\1」と「\2」で後方参照されて、「,」を間に入れた文字列に破壊的に変更するよ。

true whileと書いてあるから、string#sub!メソッドが失敗するまで繰り返し実行されるよ。つまり、3文字以上の整数文字が並んでいる限り繰り返されるから、結果的に整数を3桁ごとにカンマで区切るという動作になるんだね。

今日はここまで。ようやく/usr/lib/ruby/1.8/quickml/utils.rbを読み終えたけど、ゆっくり読んでいたらずいぶん時間がかかっちゃったね。

トラックバック - http://rubyist.g.hatena.ne.jp/muscovyduck/20060621

2006-06-20

[] QuickMLソースコードを読む(13) /usr/lib/ruby/1.8/quickml/utils.rbの続き  QuickMLのソースコードを読む(13) /usr/lib/ruby/1.8/quickml/utils.rbの続き - バリケンのRuby日記 を含むブックマーク はてなブックマーク -  QuickMLのソースコードを読む(13) /usr/lib/ruby/1.8/quickml/utils.rbの続き - バリケンのRuby日記  QuickMLのソースコードを読む(13) /usr/lib/ruby/1.8/quickml/utils.rbの続き - バリケンのRuby日記 のブックマークコメント

昨日の日記の続きだよ。

File::safe_openメソッド

class File
  def self.safe_open (filename, mode = "r")
    begin
      f = File.open(filename, mode)
      if block_given?
        yield(f)
        f.close
      else
        return f
      end
    rescue => e
      STDERR.printf "%s: %s\n", $0, e.message
      exit(1)
    end
  end
end

これは、File::safe_openメソッドの定義だね。ファイルオープンして、ブロックを与えられたら「オープンしたファイルブロック引数としてブロックを実行、その後ファイルクローズ」するよ。ブロックが与えられなければオープンしたファイルそのものを返すよ。

「rescue => e」で例外オブジェクト(Exceptionクラスのサブクラス)を捕捉できるよ。捕捉した例外オブジェクトをもとに、Exception#messageメソッドでエラー文字列を得ているよ。あと$0は起動したスクリプトの名前の文字列だったね。この二つを標準エラー出力に出力しているんだね。

今日はここまで。

トラックバック - http://rubyist.g.hatena.ne.jp/muscovyduck/20060620

2006-06-19

[] QuickMLソースコードを読む(12) /usr/lib/ruby/1.8/quickml/utils.rbの続き  QuickMLのソースコードを読む(12) /usr/lib/ruby/1.8/quickml/utils.rbの続き - バリケンのRuby日記 を含むブックマーク はてなブックマーク -  QuickMLのソースコードを読む(12) /usr/lib/ruby/1.8/quickml/utils.rbの続き - バリケンのRuby日記  QuickMLのソースコードを読む(12) /usr/lib/ruby/1.8/quickml/utils.rbの続き - バリケンのRuby日記 のブックマークコメント

昨日の日記の続きだよ。

TCPSocket#addressメソッド

class TCPSocket
  def address
    peeraddr[3]
  end

これは、TCPSocket#addressメソッドの定義だね。IPSocket#peeraddrメソッドで得られた「接続相手先ソケット情報配列」のうち、第4要素(接続相手のIPアドレス)を文字列として返すよ。

TCPSocket#hostnameメソッド

  def hostname
    peeraddr[2]
  end
end

これは、TCPSocket#hostnameメソッドの定義だね。IPSocket#peeraddrメソッドで得られた「接続相手先ソケット情報配列」のうち、第3要素(接続相手のホスト名)を文字列として返すよ。

今日はここまで。

トラックバック - http://rubyist.g.hatena.ne.jp/muscovyduck/20060619

2006-06-18

[] QuickMLソースコードを読む(11) /usr/lib/ruby/1.8/quickml/utils.rbの続き  QuickMLのソースコードを読む(11) /usr/lib/ruby/1.8/quickml/utils.rbの続き - バリケンのRuby日記 を含むブックマーク はてなブックマーク -  QuickMLのソースコードを読む(11) /usr/lib/ruby/1.8/quickml/utils.rbの続き - バリケンのRuby日記  QuickMLのソースコードを読む(11) /usr/lib/ruby/1.8/quickml/utils.rbの続き - バリケンのRuby日記 のブックマークコメント

昨日の日記の続きだよ。

String#xchomp!メソッド

class String
  def xchomp!
    self.chomp!("\n")
    self.chomp!("\r")
  end

これはString#xchomp!メソッドの定義だね。String#chomp!メソッドで、引数で指定した行区切り文字を文字列の末尾から取り除くよ。ここではまず\nを、次に\rを削除しているよ。

String#normalize_eol!メソッド

  def normalize_eol!
    self.xchomp!
    self << "\n"
  end

これはString#normalize_eol!メソッドの定義だね。先ほどのString#xchomp!メソッドで文字列末尾の改行文字を削除してから、"\n"を追加しているよ。

String#xchompメソッド

  def xchomp
    self.chomp("\n").chomp("\r")
  end
end

これはString#xchompメソッドの定義だね。String#chompメソッドで、まず\nを、次に\rを削除したオブジェクト新たに生成しているよ。このメソッドはさっきのString#xchomp!メソッドと違って破壊的じゃないところがポイントだよ。

今日はここまで。

トラックバック - http://rubyist.g.hatena.ne.jp/muscovyduck/20060618

2006-06-17

[] QuickMLソースコードを読む(10) /usr/lib/ruby/1.8/quickml/utils.rbの続き  QuickMLのソースコードを読む(10) /usr/lib/ruby/1.8/quickml/utils.rbの続き - バリケンのRuby日記 を含むブックマーク はてなブックマーク -  QuickMLのソースコードを読む(10) /usr/lib/ruby/1.8/quickml/utils.rbの続き - バリケンのRuby日記  QuickMLのソースコードを読む(10) /usr/lib/ruby/1.8/quickml/utils.rbの続き - バリケンのRuby日記 のブックマークコメント

昨日の日記の続きだよ。

IO#safe_getsメソッド

class IO
  def safe_gets (max_length = 1024)
    s = ""
    until self.eof?
      c = self.read(1)
      s << c
      if s.length > max_length
        raise TooLongLine
      end
      if c == "\n"
        return s
      end
    end
    if s.empty? then nil else s end
  end
end

これは、IO#safe_getsメソッドの定義だね。IOオブジェクトから一文字ずつ読み込んだ文字を、変数sが貼り付けられている文字列オブジェクトに追加していき、その文字列オブジェクトの長さがmax_lengthよりも長くなった場合はTooLongLine例外を発生、読み込んだ文字が"\n"であったなら変数sが貼り付けられている文字列オブジェクトを返す、という動作をするよ。

またIOオブジェクトからの読み込みがeofまで到達してしまった場合は、変数sが貼り付けられている文字列オブジェクトが空の場合はnilを、そうでなければその文字列オブジェクトを返す、という動作をするよ。

とりあえず今日はここまで。

トラックバック - http://rubyist.g.hatena.ne.jp/muscovyduck/20060617

2006-06-16

[] QuickMLソースコードを読む(9) /usr/lib/ruby/1.8/quickml/utils.rb  QuickMLのソースコードを読む(9) /usr/lib/ruby/1.8/quickml/utils.rb - バリケンのRuby日記 を含むブックマーク はてなブックマーク -  QuickMLのソースコードを読む(9) /usr/lib/ruby/1.8/quickml/utils.rb - バリケンのRuby日記  QuickMLのソースコードを読む(9) /usr/lib/ruby/1.8/quickml/utils.rb - バリケンのRuby日記 のブックマークコメント

今日からは「/usr/lib/ruby/1.8/quickml/utils.rb」のコードを読んでいくよ。

#
# quickml/utils - a part of quickml server
#
# Copyright (C) 2002-2004 Satoru Takabayashi <satoru@namazu.org>
#     All rights reserved.
#     This is free software with ABSOLUTELY NO WARRANTY.
#
# You can redistribute it and/or modify it under the terms of
# the GNU General Public License version 2.
#
$KCODE = 'e'
require 'kconv'
require 'net/smtp'
require 'ftools'

class TooLongLine < Exception; end
class IO
  def safe_gets (max_length = 1024)
    s = ""
    until self.eof?
      c = self.read(1)
      s << c
      if s.length > max_length
        raise TooLongLine
      end
      if c == "\n"
        return s
      end
    end
    if s.empty? then nil else s end
  end
end

class String
  def xchomp!
    self.chomp!("\n")
    self.chomp!("\r")
  end

  def normalize_eol!
    self.xchomp!
    self << "\n"
  end

  def xchomp
    self.chomp("\n").chomp("\r")
  end
end

class TCPSocket
  def address
    peeraddr[3]
  end

  def hostname
    peeraddr[2]
  end
end

class File
  def self.safe_open (filename, mode = "r")
    begin
      f = File.open(filename, mode)
      if block_given?
        yield(f)
        f.close
      else
        return f
      end
    rescue => e
      STDERR.printf "%s: %s\n", $0, e.message
      exit(1)
    end
  end
end

class Integer
  # commify(12345) => "12,345"
  def commify
    numstr = self.to_s
    true while numstr.sub!(/^([-+]?\d+)(\d{3})/, '\1,\2')
    return numstr
  end
end

$KCODE

$KCODE = 'e'

これは組み込み変数$KCODEで、「このRubyスクリプト中で使用する漢字コードはeucである」ことを宣言しているんだよね。

require

require 'kconv'
require 'net/smtp'
require 'ftools'

これはKernel#requireメソッドで、「/usr/lib/ruby/1.8/kconv.rb、/usr/lib/ruby/1.8/net/smtp.rb、/usr/lib/ruby/1.8/ftools.rbをRubyスクリプトとして読み込む」ということだよね。

TooLongLineクラス

class TooLongLine < Exception; end

これは、例外クラスException継承してTooLongLineクラスを定義しているよ。例外はrescue節で捕捉することができるけど、とりあえずここでは例外についての説明しないよ。

とりあえず、今日はここまで。

2006-06-15

[] QuickMLソースコードを読む(8) /usr/lib/ruby/1.8/quickml/logger.rb  QuickMLのソースコードを読む(8) /usr/lib/ruby/1.8/quickml/logger.rb - バリケンのRuby日記 を含むブックマーク はてなブックマーク -  QuickMLのソースコードを読む(8) /usr/lib/ruby/1.8/quickml/logger.rb - バリケンのRuby日記  QuickMLのソースコードを読む(8) /usr/lib/ruby/1.8/quickml/logger.rb - バリケンのRuby日記 のブックマークコメント

おとといの日記で、QuickML::Configクラスの定義ではQuickML::LoggerクラスインスタンスQuickML::GetText::Catalogクラスインスタンスを生成しているのがわかったね。

じゃあ、今日からは「QuickML::Loggerクラス」の定義が書かれている「/usr/lib/ruby/1.8/quickml/logger.rb」のコードを読んでいくよ。

#
# quickml/logger - a part of quickml server
#
# Copyright (C) 2002-2004 Satoru Takabayashi <satoru@namazu.org>
#     All rights reserved.
#     This is free software with ABSOLUTELY NO WARRANTY.
#
# You can redistribute it and/or modify it under the terms of
# the GNU General Public License version 2.
#
require 'quickml/utils'
require 'thread'

module QuickML
  class Logger
    def initialize (log_filename, verbose_mode = nil)
      @mutex = Mutex.new
      @log_file = File.safe_open(log_filename, "a")
      @log_file.sync = true
      @verbose_mode = verbose_mode
    end

    private
    def puts_log (msg)
      @mutex.synchronize {
        time = Time.now.strftime("%Y-%m-%dT%H:%M:%S")
        @log_file.puts "#{time}: #{msg}"
      }
    end

    public
    def log (msg)
      puts_log(msg)
    end

    def vlog (msg)
      puts_log(msg) if @verbose_mode
    end

    def reopen
      @mutex.synchronize {
        log_filename = @log_file.path
        @log_file.close
        @log_file = File.safe_open(log_filename, "a")
      }
    end
  end
end

じゃあ、順に読んでいこうか。

require

require 'quickml/utils'
require 'thread'

これは、Kernel#requireメソッドで「/usr/lib/ruby/1.8/quickml/utils.rb」「/usr/lib/ruby/1.8/thread.rb」をrubyスクリプトとして読んでいるよ。

QuickML::Loggerクラスのinitialize

module QuickML
  class Logger
    def initialize (log_filename, verbose_mode = nil)
      @mutex = Mutex.new
      @log_file = File.safe_open(log_filename, "a")
      @log_file.sync = true
      @verbose_mode = verbose_mode
    end

とくに説明は不要かな?Mutexスレッドロックを制御するオブジェクトだったよね。また、File::safe_openメソッドは/usr/lib/ruby/1.8/quickml/utils.rbで定義されているんだよね。あとIO#sync=メソッドは、trueにするとsyncモード(同期モードバッファリングせずにすぐに書き込むモード)になるよ。

QuickML::Logger#puts_logメソッド

    private
    def puts_log (msg)
      @mutex.synchronize {
        time = Time.now.strftime("%Y-%m-%dT%H:%M:%S")
        @log_file.puts "#{time}: #{msg}"
      }
    end

このメソッドはprivateだから、インスタンスメソッドとしては使えないよ。

Mutex#synchronizeメソッドは、与えられたブロックを実行中にスレッドロックするメソッドだね。

ブロックの中身は、現在時刻と引数で与えられたmsgを@log.fileに対してIO#putsメソッドで出力しているよ。

QuickML::Logger#logメソッド

    public
    def log (msg)
      puts_log(msg)
    end

ここからはpublicなメソッドになるから、インスタンスメソッドとして使うことが出来るよ。

QuickML::Logger#logメソッドは、さっきのprivateなQuickML::Logger#puts_logメソッドを呼び出しているよ。

QuickML::Logger#vlogメソッド

    def vlog (msg)
      puts_log(msg) if @verbose_mode
    end

QuickML::Logger#vlogメソッドは、@verbose_modeがtrueなら、privateなQuickML::Logger#puts_logメソッドを呼び出す、というものだよ。

QuickML::Logger#reopenメソッド

    def reopen
      @mutex.synchronize {
        log_filename = @log_file.path
        @log_file.close
        @log_file = File.safe_open(log_filename, "a")
      }
    end
  end
end

QuickML::Logger#reopenメソッドは、スレッドロックしてファイルを開きなおしているよ。ちなみにFile#pathファイルオープンしたときのパス指定文字列、IO#closeファイルクローズだよ。

今日はここまで。/usr/lib/ruby/1.8/quickml/logger.rbファイルは小さかったね。

トラックバック - http://rubyist.g.hatena.ne.jp/muscovyduck/20060615

2006-06-14

[] QuickMLソースコードを読む(7) /usr/lib/ruby/1.8/quickml/config.rbの続き  QuickMLのソースコードを読む(7) /usr/lib/ruby/1.8/quickml/config.rbの続き - バリケンのRuby日記 を含むブックマーク はてなブックマーク -  QuickMLのソースコードを読む(7) /usr/lib/ruby/1.8/quickml/config.rbの続き - バリケンのRuby日記  QuickMLのソースコードを読む(7) /usr/lib/ruby/1.8/quickml/config.rbの続き - バリケンのRuby日記 のブックマークコメント

昨日の日記の続きだよ。

QuickML::Config#ml_mutexメソッド

    def ml_mutex (address)
      @ml_mutexes.fetch(address) {|x|
        @ml_mutexes[x] = Mutex.new
      }
    end

これは、スレッドロックを制御するml_mutexメソッドだね。@ml_mutexes(ハッシュオブジェクト)に対して、addressを引数にしてfetchメソッドの実行を依頼するよ。

Hash#fetchは「addressをKeyとする要素があればそのValueを、なければブロックを実行(つまりaddressをKeyMutex.newをValueとするHash要素を@ml_mutexesに追加)」という動作をするよ。

QuickML::Config::loadメソッド

    def self.load (filename)
      self.new(eval(File.safe_open(filename).read))
    end
  end
end

self.loadとすることで、クラスの特異メソッド(つまりクラスメソッド)を定義しているよ。

File::safe_openメソッドは/usr/lib/ruby/1.8/quickml/utils.rbで定義されているんだよね。これは、filename文字列を引数として指定したファイルオープンして、Kernel#evalでテキストファイルをスクリプトとして評価しているよ。

このメソッドの呼び出し側は、デフォルトでは/etc/quickml/quickmlrcだったよね。/etc/quickml/quickmlrcのファイルも見てみよう。

# -*- mode: ruby -*-
mailname = File.open("/etc/mailname") do |fp| fp.read.chomp; end
Config = {
  :port => 10025,       # see /usr/share/doc/quickml/with-mta.rd
  :user => "list",
  :group => "list",
  :bind_address => "127.0.0.1",

  :smtp_host => 'mail.example.net',
  :smtp_port => 25,
  :domain => 'example.net',
  :postmaster => "postmaster@example.net",
  :info_url => "http://QuickML.com/",

  :data_dir => '/var/lib/quickml',
  :pid_file => '/var/run/quickml/quickml.pid',
  :log_file => '/var/log/quickml/quickml-log',

  :verbose_mode => true,
  :max_members => 100,
  :max_mail_length => 100 * 1024,
  :ml_life_time => 86400 * 31,
  :ml_alert_time => 86400 * 30,
  :auto_unsubscribe_count => 5,

  :sweep_interval => 3600,
  :max_threads => 10,
  :timeout => 120,
  :use_qmail_verp => false,

  :confirm_ml_creation => false, # for confirming ML creation.

  # :message_catalog => nil,  # for English messages
  :message_catalog => '/usr/share/quickml/messages.ja', # for Japanese messages
  # :authorized_creators_list => '/etc/quickml/authorized_creators',
  # :distribute_address => "dist@#{mailname}",
  # :report_address => "notify@#{mailname}"
}

一見設定ファイルのように見えるけど、実はRubyスクリプトの一部なんだねえ。

今日はここまで。ようやく/usr/lib/ruby/1.8/quickml/config.rbを読み終わったね。

トラックバック - http://rubyist.g.hatena.ne.jp/muscovyduck/20060614

2006-06-13

[] QuickMLソースコードを読む(6) /usr/lib/ruby/1.8/quickml/config.rb  QuickMLのソースコードを読む(6) /usr/lib/ruby/1.8/quickml/config.rb - バリケンのRuby日記 を含むブックマーク はてなブックマーク -  QuickMLのソースコードを読む(6) /usr/lib/ruby/1.8/quickml/config.rb - バリケンのRuby日記  QuickMLのソースコードを読む(6) /usr/lib/ruby/1.8/quickml/config.rb - バリケンのRuby日記 のブックマークコメント

今日からは「QuickML::Configクラス」の定義が書かれている「/usr/lib/ruby/1.8/quickml/config.rb」のコードを読んでいくよ。

require

require 'quickml/utils'
require 'quickml/logger'
require 'quickml/gettext'

これは、Kernel#requireメソッドで「/usr/lib/ruby/1.8/quickml/utils.rb」「/usr/lib/ruby/1.8/quickml/logger.rb」「/usr/lib/ruby/1.8/quickml/gettext.rb」をrubyスクリプトとして読んでいるよ。

QuickML::Configクラスのinitialize

じゃあ、QuickML::Configクラスインスタンスが生成されるときに呼び出されるinitializeメソッドを見てみるよ。

    def initialize (config = {})
      @data_dir = config[:data_dir]
      @smtp_host = config[:smtp_host]
      @domain = config[:domain]

まず、引数デフォルトでは空のHashオブジェクト)にconfigというラベル(変数)を貼り付けているよ。

次に、config[:data_dir]とconfig[:smtp_host]とconfig[:domain]に、@data_dirと@smtp_hostと@domainというラベル(インスタンス変数)を貼り付けているよ。

      raise ArgumentError if @data_dir.nil?
      raise ArgumentError if @smtp_host.nil?
      raise ArgumentError if @domain.nil?

これはKernel#raiseによって、@data_dir、@smtp_host、@domainが貼られているオブジェクトnilであるなら、ArgumentError例外を発生させる、ということだよ。

      @smtp_port = (config[:smtp_port] or 25)
      @postmaster = (config[:postmaster] or "postmaster@#{domain}")
      @info_url = (config[:info_url] or "http://QuickML.com/")

      @pid_file = (config[:pid_file] or "/var/run/quickml.pid")
      @max_members = (config[:max_members] or 100)
      @max_mail_length = (config[:max_mail_length] or 100 * 1024) # 100KB
      @max_ml_mail_length = @max_mail_length
      @ml_life_time = (config[:ml_life_time] or 86400 * 30)
      @ml_alert_time = (config[:ml_alert_time] or 86400 * 29)
      @sweep_interval = (config[:sweep_interval] or 3600)
      @allowable_error_interval = (config[:allowable_error_interval] or 8600)
      @max_threads = (config[:max_threads] or 10) # number of working threads
      @timeout = (config[:timeout] or 120)
      @auto_unsubscribe_count = (config[:auto_unsubscribe_count] or 5)

      @log_file = (config[:log_file] or "/var/log/quickml-log")
      verbose_mode = config[:verbose_mode]

このへんは見たまんまだから、説明いらないよね?

      @logger = Logger.new(@log_file, verbose_mode)
      @ml_mutexes = Hash.new
      @catalog = if config[:message_catalog]
                   GetText::Catalog.new(config[:message_catalog])
                 else
                   nil
                 end

おや、QuickML::LoggerクラスインスタンスQuickML::GetText::Catalogクラスインスタンスを生成しているね。QuickML::Loggerクラスの定義は/usr/lib/ruby/1.8/quickml/logger.rbで、QuickML::GetText::Catalogクラスの定義は/usr/lib/ruby/1.8/quickml/gettext.rbでされているよ。そっちを見るのはまた今度にしよう。

      @port = (config[:port] or 25)
      @bind_address = (config[:bind_address] or "0.0.0.0")
      @user = (config[:user] or "root")
      @group = (config[:group] or "root")
      @use_qmail_verp = (config[:use_qmail_verp] or false)

      @authorized_creators_list = config[:authorized_creators_list]
      @distribute_address = config[:distribute_address]
      @report_address = config[:report_address]

      charset = @catalog.charset if @catalog
      @content_type = "text/plain"

      @confirm_ml_creation = (config[:confirm_ml_creation] or false)

このへんも説明いらないよねえ。見たまんまだし。

      instance_variables.each {|name|
        self.class.class_eval { attr_reader name.delete('@') }
      }
    end

ん、最後で面白いことをしているね。Object#instance_variablesメソッドは、オブジェクトインスタンス変数名を文字列の配列として返すよ。

そして、Module#class_evalメソッドで、各インスタンス変数名から「@」を取ったものをModule#attr_readerによってアクセサ定義しているよ。すべてのインスタンス変数にアクセサを設定したいときに便利だね。

とりあえず今日はここまで。

2006-06-12

[] QuickMLソースコードを読む(5) /usr/lib/ruby/1.8/quickml.rb  QuickMLのソースコードを読む(5) /usr/lib/ruby/1.8/quickml.rb - バリケンのRuby日記 を含むブックマーク はてなブックマーク -  QuickMLのソースコードを読む(5) /usr/lib/ruby/1.8/quickml.rb - バリケンのRuby日記  QuickMLのソースコードを読む(5) /usr/lib/ruby/1.8/quickml.rb - バリケンのRuby日記 のブックマークコメント

昨日の日記で、/usr/sbin/quickmlでは「QuickML::Configクラスインスタンス」「QuickML::Serverクラスインスタンス」「QuickML::Sweeperクラスインスタンス」を生成していることがわかったよ。

じゃあ、とりあえずその3つのクラスの定義を見ていけば、全体の流れがわかるかな?

‥‥とその前に、/usr/sbin/quickmlで「require 'quickml'」とあったから、「/usr/lib/ruby/1.8/quickml.rb」ファイルの中身を見てみよう。

/usr/lib/ruby/1.8/quickml.rb

require 'quickml/utils'
require 'quickml/config'
require 'quickml/core'
require 'quickml/gettext'
require 'quickml/logger'
require 'quickml/mail'
require 'quickml/server'
require 'quickml/sweeper'
require 'quickml/version'

なるほど、Kernel#requireメソッドで「/usr/lib/ruby/1.8/quickml/*.rb」をrubyスクリプトとして一括で読んでいるんだね。

ということで、ようやく本番。今日は「QuickML::Configクラス」の定義が書かれている「/usr/lib/ruby/1.8/quickml/config.rb」のコードを読んでみよう!

/usr/lib/ruby/1.8/quickml/config.rb

#
# quickml/config - a part of quickml server
#
# Copyright (C) 2002-2004 Satoru Takabayashi <satoru@namazu.org>
#     All rights reserved.
#     This is free software with ABSOLUTELY NO WARRANTY.
#
# You can redistribute it and/or modify it under the terms of
# the GNU General Public License version 2.
#

require 'quickml/utils'
require 'quickml/logger'
require 'quickml/gettext'

module QuickML
  class Config
    def initialize (config = {})
      @data_dir = config[:data_dir]
      @smtp_host = config[:smtp_host]
      @domain = config[:domain]

      raise ArgumentError if @data_dir.nil?
      raise ArgumentError if @smtp_host.nil?
      raise ArgumentError if @domain.nil?

      @smtp_port = (config[:smtp_port] or 25)
      @postmaster = (config[:postmaster] or "postmaster@#{domain}")
      @info_url = (config[:info_url] or "http://QuickML.com/")

      @pid_file = (config[:pid_file] or "/var/run/quickml.pid")
      @max_members = (config[:max_members] or 100)
      @max_mail_length = (config[:max_mail_length] or 100 * 1024) # 100KB
      @max_ml_mail_length = @max_mail_length
      @ml_life_time = (config[:ml_life_time] or 86400 * 30)
      @ml_alert_time = (config[:ml_alert_time] or 86400 * 29)
      @sweep_interval = (config[:sweep_interval] or 3600)
      @allowable_error_interval = (config[:allowable_error_interval] or 8600)
      @max_threads = (config[:max_threads] or 10) # number of working threads
      @timeout = (config[:timeout] or 120)
      @auto_unsubscribe_count = (config[:auto_unsubscribe_count] or 5)

      @log_file = (config[:log_file] or "/var/log/quickml-log")
      verbose_mode = config[:verbose_mode]
      @logger = Logger.new(@log_file, verbose_mode)
      @ml_mutexes = Hash.new
      @catalog = if config[:message_catalog]
                   GetText::Catalog.new(config[:message_catalog])
                 else
                   nil
                 end

      @port = (config[:port] or 25)
      @bind_address = (config[:bind_address] or "0.0.0.0")
      @user = (config[:user] or "root")
      @group = (config[:group] or "root")
      @use_qmail_verp = (config[:use_qmail_verp] or false)

      @authorized_creators_list = config[:authorized_creators_list]
      @distribute_address = config[:distribute_address]
      @report_address = config[:report_address]

      charset = @catalog.charset if @catalog
      @content_type = "text/plain"

      @confirm_ml_creation = (config[:confirm_ml_creation] or false)

      instance_variables.each {|name|
        self.class.class_eval { attr_reader name.delete('@') }
      }
    end

    def ml_mutex (address)
      @ml_mutexes.fetch(address) {|x|
        @ml_mutexes[x] = Mutex.new
      }
    end

    def self.load (filename)
      self.new(eval(File.safe_open(filename).read))
    end
  end
end

とりあえず今日はここまでで、解説は明日。

トラックバック - http://rubyist.g.hatena.ne.jp/muscovyduck/20060612

2006-06-11

[] QuickMLソースコードを読む(4) /usr/sbin/quickmlの続き  QuickMLのソースコードを読む(4) /usr/sbin/quickmlの続き - バリケンのRuby日記 を含むブックマーク はてなブックマーク -  QuickMLのソースコードを読む(4) /usr/sbin/quickmlの続き - バリケンのRuby日記  QuickMLのソースコードを読む(4) /usr/sbin/quickmlの続き - バリケンのRuby日記 のブックマークコメント

昨日のエントリの続きだよ。ようやくこれで/usr/sbin/quickmlを読み終えたことになるね。

mainメソッド

def main (argv)
  config_file = if argv.length == 1 then
                  argv.first
                else
                  File.join("/etc/quickml", "quickmlrc")
                end
  config = QuickML::Config::load(config_file)
  check_directory(config.data_dir)

  be_daemon
  be_secure(config)

  server  = QuickML::Server.new(config)
  sweeper = QuickML::Sweeper.new(config)
  trap(:TERM) { server.shutdown; sweeper.shutdown }
  trap(:INT)  { server.shutdown; sweeper.shutdown }
  trap(:HUP)  { config.logger.reopen }
  t = Thread.new { sweeper.start }
  t.abort_on_exception = true
  server.start
end
main(ARGV)

最後にmain(ARGV)でmainメソッドをARGVコマンドライン引数)を引数として呼び出しているから、結局このスクリプトメイン部分はこのmainメソッドということになるね。

まず初めに、コマンドライン引数がひとつの場合はその引数として得られた文字列を、そうでなければ"/etc/quickml/quickmlrc"という文字列をconfig_fileとしている(文字列オブジェクトにconfig_fileラベルを貼り付ける)んだね。

次に、QuickML::Config::loadメソッド(loadメソッドはQuickML::Configクラスクラスメソッドで、/usr/lib/ruby/1.8/quickml/config.rbで定義されているよ)で、config_file文字列を引数として実行するとその文字列で指定したファイルを読み込んでQuickML::Configオブジェクトが生成されるみたい。

次におとといの日記で定義したcheck_directoryメソッドでconfig.data_dirディレクトリQuickML::Config#data_dirによるアクセサでインスタンス変数を読んでいる)の存在を確認して、おとといの日記で定義したbe_daemonメソッドでデーモン化、昨日の日記で定義したbe_secureメソッドでプロセスファイルの権限を安全なものに変更しているよ。

次に、QuickML::ServerクラスインスタンスQuickML::SweeperクラスインスタンスconfigオブジェクトQuickML::Configクラスインスタンス)を引数として生成しているよ。QuickML::Serverクラスは/usr/lib/ruby/1.8/quickml/server.rbで、QuickML::Sweeperクラスは/usr/lib/ruby/1.8/quickml/sweeper.rbで定義されているみたい。

ちなみにQuickML::ServerクラスインスタンスQuickMLメーリングリストサーバ自体、QuickML::Sweeperクラスインスタンスは、「あと○日投稿がないとメーリングリストが消滅します」とか、その○日後までに投稿がなかったときに実際にメーリングリスト削除したりとかいった処理をしているみたいだよ。

その次にKernel#trapメソッドでTERMトラップ、INTトラップ、HUPトラップを受けたときのそれぞれの挙動を定義しているよ。具体的にはTERMかINTトラップを受けたらserverとsweeperを停止(QuickML::Server#shutdownメソッド、QuickML::Server#sweeperメソッド)、HUPトラップを受けたらlogファイルを開きなおす(QuickML::Config#loggerメソッド(アクセサ)でアクセスしたQuickML::Loggerオブジェクトに対して、QuickML::Logger#reopenメソッドを使って開きなおしている)、という動作をするみたい。

その次に別スレッドでsweeperを起動(QuickML::Sweeper#startメソッド)して、Thread#abort_on_exception=メソッドによってスレッドが例外によって終了した時にはインタプリタ全体を中断するように設定しているよ。

最後にQuickML::Server#startメソッドでサーバを起動しているよ。

この/usr/sbin/quickmlを読むと、なんとなくこのスクリプト全体の挙動はわかったような気がするよ。

とりあえず今日はここまで。うーん、全部読むのは大変そう。読むポイントを絞ったほうがいいかな?

トラックバック - http://rubyist.g.hatena.ne.jp/muscovyduck/20060611

2006-06-10

[] QuickMLソースコードを読む(3) /usr/sbin/quickmlの続き  QuickMLのソースコードを読む(3) /usr/sbin/quickmlの続き - バリケンのRuby日記 を含むブックマーク はてなブックマーク -  QuickMLのソースコードを読む(3) /usr/sbin/quickmlの続き - バリケンのRuby日記  QuickMLのソースコードを読む(3) /usr/sbin/quickmlの続き - バリケンのRuby日記 のブックマークコメント

昨日のエントリの続きだよ。

be_secureメソッド

def be_secure (config)
  return unless Process.uid == 0
  uid = Etc::getpwnam(config.user).uid
  gid = Etc::getgrnam(config.group).gid
  touch(config.pid_file)
  touch(config.log_file)
  File.chown(uid, gid, config.data_dir)
  File.chown(uid, gid, config.pid_file)
  File.chown(uid, gid, config.log_file)
  Process.uid  = uid
  Process.gid  = gid
  Process.euid = uid
end

これは、プロセスファイルの権限を安全なものに変更するbe_secureメソッドの定義だね。まずProcess::uidメソッドでプロセスuidを調べて、0でないなら(rootじゃないなら)メソッド終了。rootの場合は、configオブジェクトuserインスタンスにあるユーザー名のuidgidEtc::getpwnamメソッドとEtc::getgrnamメソッドで/etc以下から取得して、昨日の日記で定義したtouchメソッドで空のpidファイルlogファイルを作成して、configオブジェクトのdata_dir、pid_file、log_fileインスタンス(アクセサQuickML::Config#data_dir、QuickML::Config#pid_file、QuickML::Config#log_fileを使ってアクセス)で指定するファイルまたはディレクトリのオーナーを先ほどのuid,gidに変更して、最後にProcess::uidメソッドとProcess::gidメソッドを使ってプロセスの実ユーザID、実効ユーザIDuidに設定、Process::euidメソッドを使って実グループIDgidに設定しているよ。

要するに、rootで起動したとしてもプロセスの権限や生成するファイルディレクトリを安全なユーザーグループの権限にする、という動作をするんだね。

とりあえず今日はここまで。

トラックバック - http://rubyist.g.hatena.ne.jp/muscovyduck/20060610

2006-06-09

[] QuickMLソースコードを読む(2) /usr/sbin/quickmlの続き  QuickMLのソースコードを読む(2) /usr/sbin/quickmlの続き - バリケンのRuby日記 を含むブックマーク はてなブックマーク -  QuickMLのソースコードを読む(2) /usr/sbin/quickmlの続き - バリケンのRuby日記  QuickMLのソースコードを読む(2) /usr/sbin/quickmlの続き - バリケンのRuby日記 のブックマークコメント

昨日のエントリの続きだよ。

check_directoryメソッド

def check_directory (dir)
  error("#{dir}: No such directory") unless File.directory?(dir)
  error("#{dir}: is not writable")   unless File.writable?(dir)
end

これは、ディレクトリをチェックするcheck_directoryメソッドの定義だね。File::directory?メソッドで引数で指定したディレクトリをチェックしてディレクトリ存在しないときと、File::writable?メソッドで引数で指定したディレクトリをチェックして書き込み不可のときに昨日のエントリで見たerrorメソッドを使ってエラーを出力するよ。

be_daemonメソッド

def be_daemon
  exit!(0) if fork
  Process::setsid
  exit!(0) if fork
  Dir::chdir("/")
  File::umask(022)
  STDIN.reopen("/dev/null",  "r+")
  STDOUT.reopen("/dev/null", "r+")
  STDERR.reopen("/dev/null", "r+")
end

これは、自身のプロセスデーモン化するbe_daemonメソッドだね。「Rubyレシピブック 268の技」の「レシピ221 デーモンになる」で紹介されているdaemonメソッドと基本的には同じだね。

まずKernel#forkメソッドでforkすることで、自分がプロセスグループリーダーではないことを保障するよ。次にProcess::setsidメソッドで制御端末を切り離したあと、制御端末を再び得ることがないように、もう一度forkしているよ。さらにDir::chdirメソッドでカレントディレクトリを安全な場所("/")に移動して、File::umaskメソッドで以降で生成するファイルumaskを022に設定するよ。最後にIO#reopenメソッドで標準入力標準出力標準エラー出力を/dev/nullに設定してデーモン化が完了するよ。

ちょっと気になるのが、クラスメソッドを「::」とするコーディングスタイル。最近のコーディングスタイルに合わせるなら、「.」にしたほうがいいかも。

touchメソッド

def touch (filename)
  File.safe_open(filename, "a").close
end

これは、空のファイルを作るtouchメソッドの定義だね。引数で指定したファイルを開いて閉じることで、空のファイルが作られるよ。ちなみにFile#safe_openメソッドは/usr/lib/ruby/1.8/quickml/utils.rbで定義されているよ。

今日はここまで。

2006-06-08

[] QuickMLソースコードを読む(1) /usr/sbin/quickml  QuickMLのソースコードを読む(1) /usr/sbin/quickml - バリケンのRuby日記 を含むブックマーク はてなブックマーク -  QuickMLのソースコードを読む(1) /usr/sbin/quickml - バリケンのRuby日記  QuickMLのソースコードを読む(1) /usr/sbin/quickml - バリケンのRuby日記 のブックマークコメント

なんかフレームワークとしてはRuby on RailsActionMailerを使って作ればいいのかな?うーん、やっぱりRuby on Rails勉強をまじめにやったほうがいいのかも。

それはさておき、せっかくだからこの間インストールしたQuickMLソースコードを読んでみることにするよ。まずは最初に起動される「/usr/sbin/quickml」から。

/usr/sbin/quickml

#! /usr/bin/ruby1.8
# -*- mode: ruby -*-
#
# quickml - an easy-to-use mailing list server
#
# Copyright (C) 2002-2004 Satoru Takabayashi <satoru@namazu.org>
#     All rights reserved.
#     This is free software with ABSOLUTELY NO WARRANTY.
#
# You can redistribute it and/or modify it under the terms of
# the GNU General Public License version 2.
#

$KCODE = "e"
require 'quickml'

def error (msg)
  STDERR.puts "#{$0}: #{msg}"
  exit(1)
end

def show_usage
  puts "Usage: quickml <data directory> <smtp server> <domain name>"
end

def check_directory (dir)
  error("#{dir}: No such directory") unless File.directory?(dir)
  error("#{dir}: is not writable")   unless File.writable?(dir)
end

def be_daemon
  exit!(0) if fork
  Process::setsid
  exit!(0) if fork
  Dir::chdir("/")
  File::umask(022)
  STDIN.reopen("/dev/null",  "r+")
  STDOUT.reopen("/dev/null", "r+")
  STDERR.reopen("/dev/null", "r+")
end

def touch (filename)
  File.safe_open(filename, "a").close
end

def be_secure (config)
  return unless Process.uid == 0
  uid = Etc::getpwnam(config.user).uid
  gid = Etc::getgrnam(config.group).gid
  touch(config.pid_file)
  touch(config.log_file)
  File.chown(uid, gid, config.data_dir)
  File.chown(uid, gid, config.pid_file)
  File.chown(uid, gid, config.log_file)
  Process.uid  = uid
  Process.gid  = gid
  Process.euid = uid
end

def main (argv)
  config_file = if argv.length == 1 then
                  argv.first
                else
                  File.join("/etc/quickml", "quickmlrc")
                end
  config = QuickML::Config::load(config_file)
  check_directory(config.data_dir)

  be_daemon
  be_secure(config)

  server  = QuickML::Server.new(config)
  sweeper = QuickML::Sweeper.new(config)
  trap(:TERM) { server.shutdown; sweeper.shutdown }
  trap(:INT)  { server.shutdown; sweeper.shutdown }
  trap(:HUP)  { config.logger.reopen }
  t = Thread.new { sweeper.start }
  t.abort_on_exception = true
  server.start
end
main(ARGV)

まずは上から読んでいこうかな。

$KCODE

$KCODE = "e"

これは組み込み変数$KCODEで、「このRubyスクリプト中で使用する漢字コードはeucである」ことを宣言しているんだよね。

require

require 'quickml'

これはKernel#requireメソッドで、「/usr/lib/ruby/1.8/quickml.rbをRubyスクリプトとして読み込む」ということだよね。


errorメソッド

def error (msg)
  STDERR.puts "#{$0}: #{msg}"
  exit(1)
end

これは、エラーメッセージを出力するerrorメソッドの定義だね。標準エラー出力(組み込み定数STDERR)に「コマンド名: メッセージ」を出力して、Kernel#exitメソッドで「1」を戻り値としてスクリプトを終了するよ。

show_usageメソッド

def show_usage
  puts "Usage: quickml <data directory> <smtp server> <domain name>"
end

これは、コマンドの使用方法を出力するshow_usageメソッドの定義だね。単にputsで上記の文字列を出力させるだけのメソッド。でもこのメソッド、どこからも呼ばれていないみたいなんだよね。昔のバージョンの名残かな?

とりあえず今日はここまで。うーん、先は長いなあ。

トラックバック - http://rubyist.g.hatena.ne.jp/muscovyduck/20060608

2006-06-07

[] メールアプリケーション開発フレームワークってないのかな?  メールアプリケーション開発フレームワークってないのかな? - バリケンのRuby日記 を含むブックマーク はてなブックマーク -  メールアプリケーション開発フレームワークってないのかな? - バリケンのRuby日記  メールアプリケーション開発フレームワークってないのかな? - バリケンのRuby日記 のブックマークコメント

QuickMLみたいな「Webアプリケーションメールを併用するんじゃなくて、管理とかも含めてメールだけで完結するアプリケーション」を開発したいと思っているんだけど、「メールアプリケーション開発フレームワーク」ってないのかなあ。

具体的に「作りたいな~」と思っているのは、今のところ次のようなアプリケーションだよ。

携帯電話メールだけでメールマガジンが発行できたり、人狼BBSで遊べたりしたら楽しいと思うんだけど、どうかな?

そういうメールベースアプリケーションを開発するためのフレームワークみたいなのはないかなあ。

[] メールだけで完結するメールマガジンシステム「QuickMM(クイックメルマガ)」  メールだけで完結するメールマガジンシステム「QuickMM(クイックメルマガ)」 - バリケンのRuby日記 を含むブックマーク はてなブックマーク -  メールだけで完結するメールマガジンシステム「QuickMM(クイックメルマガ)」 - バリケンのRuby日記  メールだけで完結するメールマガジンシステム「QuickMM(クイックメルマガ)」 - バリケンのRuby日記 のブックマークコメント

ということで、まずはメールだけで完結するメールマガジンシステム「QuickMM(クイックメルマガ)」(QuickMLのネーミングのパクリ)を作ってみたいと思うよ。

でも、アプリケーションの開発なんてやったことないから、何からはじめたらいいかよくわからないなあ。まずは仕様を決めたらいいのかな?

こんな感じのメールアプリRubyで作ってみることにするよ。すでにQuickMLというシステムがあるから、色々とソースコードを参考にすればできるかも。

sabmeisabmei2006/06/08 18:54興味のあるメルマガを探すっていう過程も重要ですよね.
検索用メールアドレスにキーワードのメールを送ると
関連するメルマガのリストを送ってくれるというのもあるといいかも.
ニワンゴみたいですけど.

muscovyduckmuscovyduck2006/06/08 23:46sabmeiさん>
コメントありがとうございます!ゆくゆくは実現したいですねえ。

2006-06-06

[] TMailライブラリ  TMailライブラリ - バリケンのRuby日記 を含むブックマーク はてなブックマーク -  TMailライブラリ - バリケンのRuby日記  TMailライブラリ - バリケンのRuby日記 のブックマークコメント

以前の日記で教えてもらったTMailライブラリを使えば、MIMEエンコードデコードができるみたいだよ。

TMail::Mail#subjectとTMail::Mail#subject=を使えばいいのかな?まだTMailライブラリをよく理解していないので、またこんど。

トラックバック - http://rubyist.g.hatena.ne.jp/muscovyduck/20060606

2006-06-05

[] 電子メールのSubjectをMIMEエンコードデコード  電子メールのSubjectをMIMEエンコード/デコード - バリケンのRuby日記 を含むブックマーク はてなブックマーク -  電子メールのSubjectをMIMEエンコード/デコード - バリケンのRuby日記  電子メールのSubjectをMIMEエンコード/デコード - バリケンのRuby日記 のブックマークコメント

電子メールのSubjectをMIMEエンコードデコードしたいと思ったよ。

とりあえずQuickMLソースコードで、MIMEエンコードデコードしている部分を見てみることにしたよ。

# aptitude install quickml
$ less /usr/lib/ruby/1.8/quickml/mail.rb

MIMEエンコードをするencode_fieldメソッドは、次のように定義されていたよ。

def encode_field (field)
  field.toeuc.gsub(/[ -瑤]\S*\s*/) {|x|
    x.scan(/.{1,10}/).map {|y|
      "=?iso-2022-jp?B?" + y.tojis.to_a.pack('m').chomp + "?="
    }.join("\n ")
  }
end

ふーむ、10文字単位に切り出しているのには、なにか意味があるのかな?あと正規表現の「[ -瑤]」ってなんだろう。

次はMIMEデコードをするdecode_subjectメソッドをみてみよう。

def decode_subject (subject)
  NKF.nkf("-e", subject.gsub(/\n\s*/, " "))
end

ん、これだとさっきの10文字単位に切り出した文字列を連結するときに、10文字単位で空白が入っちゃいそうだね。いいのかなあ。

標準ライブラリでは、MIMEエンコードデコードするようなものはないのかな?

のりつぐのりつぐ2006/06/07 16:06「瑤」は第2水準の最後の文字みたいですね。全角スペースは最初のコードなので「[ -瑤]」で全角文字全体なんでしょうね〜

muscovyduckmuscovyduck2006/06/07 19:24のりつぐさん>
コメントありがとうございます!なるほど、「瑤」は第2水準の最後の文字文字なんですね。それにしても、コード体系に依存しない「全角文字全体」を表す正規表現が欲しいところですね。

トラックバック - http://rubyist.g.hatena.ne.jp/muscovyduck/20060605

2006-06-04

[] リスト構造の要素から、条件を満たす要素を取り出す  リスト構造の要素から、条件を満たす要素を取り出す - バリケンのRuby日記 を含むブックマーク はてなブックマーク -  リスト構造の要素から、条件を満たす要素を取り出す - バリケンのRuby日記  リスト構造の要素から、条件を満たす要素を取り出す - バリケンのRuby日記 のブックマークコメント

昨日エントリの続きだよ。

じゃあ、いよいよ「あるリスト構造に含まれる要素のうち、条件を満たす要素だけで構成されたリスト新たに生成する」というメソッドfind_allを定義してみるよ。

  def find_all(x)
    result = List.new
    self.each do |i|
      result.add_last(i) if x.call(i)
    end
    return result
  end

xには手続きオブジェクトを与えることで、条件を指定することにしたよ。ブロック付きメソッドのおかげで、Javaソースコードよりもずいぶんシンプルに書けたね。

じゃあ、1から6までの整数を要素として持つリストを作ってから、2の倍数と3の倍数を取り出すサンプルを作ってみよう!

class List
  def initialize
    @cdr = nil
    @car = nil
  end
  attr_accessor :cdr, :car

  def add_last(x)
    a = self
    a = a.car until a.car.nil?
    a.car = List.new
    a.car.cdr = x
  end

  def size
    a = self
    i = 0
    i += 1 while a = a.car
    return i
  end

  def each
    a = self.car
    self.size.times do
      yield a.cdr
      a = a.car
    end
  end

  def find_all(x)
    result = List.new
    self.each do |i|
      result.add_last(i) if x.call(i)
    end
    return result
  end
end

x = Proc.new {|a| a % 2 == 0 ? true : false }
y = Proc.new {|b| b % 3 == 0 ? true : false }

z = List.new
z.add_last(1)
z.add_last(2)
z.add_last(3)
z.add_last(4)
z.add_last(5)
z.add_last(6)

m = z.find_all(x)
n = z.find_all(y)

m.each do |i|
  puts i
end

puts

n.each do |i|
  puts i
end

実行結果だよ。

2
4
6

3
6

確かに2の倍数と3の倍数が取り出せたね。

追記:リファクタリングしていただきました。ありがとうございます!

トラックバック - http://rubyist.g.hatena.ne.jp/muscovyduck/20060604

2006-06-03

[] Rubyリスト構造の実装  Rubyでリスト構造の実装 - バリケンのRuby日記 を含むブックマーク はてなブックマーク -  Rubyでリスト構造の実装 - バリケンのRuby日記  Rubyでリスト構造の実装 - バリケンのRuby日記 のブックマークコメント

ようやく「ふつうのHaskellプログラミング」を買うことができたよ。

ただ、HaskellHaskell以外のプログラミング言語を比較するとき、サンプルコードがJavaで書かれているみたいだよ。なので、せっかくだからサンプルコードをRuby移植してみることにするよ。

まずは5ページ目のサンプルコードをRuby移植してみるよ。ここでは「あるリスト構造に含まれる要素のうち、条件を満たす要素だけで新たにリストを生成する」というメソッドがサンプルとして掲載されているよ。

そのまえに、Rubyリスト構造を実装しないとダメだよね。リスト構造を実装するには「値」と「次の要素へのラベル」を要素として持つオブジェクトがあればよさそうだから、次のようにListクラスを定義すればいいかな。

class List
  def initialize
    @cdr = nil
    @car = nil
  end
end

ここで、@cdrは「値」、@carは「次の要素へのラベル」を表すインスタンス変数とするよ。

このままだと値が設定できないよね。じゃあ、アクセサを追加しよう。

class List
  def initialize
    @cdr = nil
    @car = nil
  end
  attr_accessor :cdr, :car
end

これで、値が追加できるようになったね。でもこのままだとリストに要素を追加するのが面倒だから、リストの最後に要素を追加するadd_lastメソッドも定義してみるよ。

class List
  def initialize
    @cdr = nil
    @car = nil
  end
  attr_accessor :cdr, :car

  def add_last(x)
    a = self
    a = a.car until a.car.nil?
    a.car = List.new
    a.car.cdr = x
  end
end

リストのサイズも知りたくなるよね。ということで、リストの要素を数えるsizeメソッドも追加してみたよ。

class List
  def initialize
    @cdr = nil
    @car = nil
  end
  attr_accessor :cdr, :car

  def add_last(x)
    a = self
    a = a.car until a.car.nil?
    a.car = List.new
    a.car.cdr = x
  end

  def size
    a = self
    i = 0
    i += 1 while a = a.car
    return i
  end
end

リストの各要素に対して繰り返し処理をするイテレータが欲しいよね。ということで、ブロックを与えるとリストの各要素に繰り返し処理をするeachメソッドも追加してみたよ。

class List
  def initialize
    @cdr = nil
    @car = nil
  end
  attr_accessor :cdr, :car

  def add_last(x)
    a = self
    a = a.car until a.car.nil?
    a.car = List.new
    a.car.cdr = x
  end

  def size
    a = self
    i = 0
    i += 1 while a = a.car
    return i
  end

  def each
    a = self.car
    self.size.times do
      yield a.cdr
      a = a.car
    end
  end
end

じゃあ、このへんでオブジェクトを生成してうまく動くか試してみよう!

class List
  def initialize
    @cdr = nil
    @car = nil
  end
  attr_accessor :cdr, :car

  def add_last(x)
    a = self
    a = a.car until a.car.nil?
    a.car = List.new
    a.car.cdr = x
  end

  def size
    a = self
    i = 0
    i += 1 while a = a.car
    return i
  end

  def each
    a = self.car
    self.size.times do
      yield a.cdr
      a = a.car
    end
  end
end

z = List.new
z.add_last('Ruby')
z.add_last('Haskell')
z.add_last('Java')
z.add_last('Perl')
z.add_last('Python')

puts z.size

z.each do |i|
  puts i
end

実行結果だよ。

5
Ruby
Haskell
Java
Perl
Python

まだまだ追加したいメソッドはいっぱいあるけど、とりあえず今日はここまで。

トラックバック - http://rubyist.g.hatena.ne.jp/muscovyduck/20060603

2006-06-02

[] TMailライブラリTimeライブラリ  TMailライブラリ、Timeライブラリ - バリケンのRuby日記 を含むブックマーク はてなブックマーク -  TMailライブラリ、Timeライブラリ - バリケンのRuby日記  TMailライブラリ、Timeライブラリ - バリケンのRuby日記 のブックマークコメント

昨日の日記トラックバックをいただきました。ありがとうございます!

こちらではTMailというライブラリについて教えてもらったよ。このライブラリ、すごく便利そう。

またこちらでは、timeライブラリを使うことを教えてもらったよ。

require 'time'

puts Time.now.rfc2822

実行結果だよ。

Fri, 02 Jun 2006 08:51:02 +0900

うーん、いちど時間をかけてリファレンスマニュアルを全部読んだほうがいいのかも。

トラックバック - http://rubyist.g.hatena.ne.jp/muscovyduck/20060602

2006-06-01

[] Time#strftime  Time#strftime - バリケンのRuby日記 を含むブックマーク はてなブックマーク -  Time#strftime - バリケンのRuby日記  Time#strftime - バリケンのRuby日記 のブックマークコメント

Time#strftimeを使えば、Timeオブジェクトを元にして、時刻を表示する文字列を好きなフォーマットで得ることができるよ。

puts Time.now.strftime("%a, %d %b %Y %H:%M:%S %Z")

とやると、実行結果は

Thu, 01 Jun 2006 10:15:19 JST

となるよ。

電子メールとかでよくある「+0900」とかにしたいときはどうすればいいのかな。Time#utc_offsetを使う手もあるけど、もっと簡単にできないかなあ。

追記:解決しました