tropy/tropy.rb

#
# tropy.rb - Tropy in Ruby, Version 0.2
#
# Copyright (C) 2005,2006 by Hiroshi Yuki.
# http://www.hyuki.com/tropy/
#
# You can redistribute and/or modify it under the same terms as Ruby.
#
require "cgi"
require "nkf"
require "pstore"

CHARSET = "Shift_JIS"
NKF_OPTION = "-m0 -s"

BEGIN { $defout.binmode }

module Tropy

  # idとメッセージ(タイトルと本体)を管理
  class Database < PStore
    INDEX = "index" # id一覧保存用のキー

    def initialize(filename)
      super(filename)
      transaction do
        unless self[INDEX]
          self[INDEX] = []
        end
      end
    end

    def empty?
      transaction(true) do
        self[INDEX].length == 0
      end
    end

    # 存在するidからランダムに一個選択
    def random_id
      transaction(true) do
        size = self[INDEX].length
        self[INDEX][rand(size)]
      end
    end

    # 存在の有無にかかわらずランダムなidを一個生成
    def create_id
      sprintf("%08d", rand(10 ** 8))
    end

    # idを追加
    def add_id(id)
      unless self[INDEX].index(id)
        self[INDEX] << id
      end
    end

    # idを削除
    def delete_id(id)
      index = self[INDEX].index(id)
      if index
        self[INDEX].delete_at(index)
      end
    end

    # idのページにメッセージmを保存
    def set_msg(id, m)
      id = id.to_s
      m = NKF::nkf(NKF_OPTION, m.to_s)
      transaction do
        if m.length > 0
          self[id] = m
          add_id(id)
        else
          self.delete(id)
          delete_id(id)
        end
      end
    end

    # idのページのメッセージ
    def msg(id)
      transaction(true) do
        self[id.to_s].to_s
      end
    end

    # idのページのタイトル(一行目)
    def title(id)
      if msg(id) =~ /^(.*)$/
        $1
      else
        ""
      end
    end

    # idのページの本体(二行目以降)
    def body(id)
      msg(id).sub!(/^.*$/, "")
    end
  end

  class Tropy
    def initialize(cgi, db)
      @db = db
      begin
        cgi.params.each_key do |k|
          if k =~ /^(\d{8})$/
            @id = $1
            do_read
          elsif k =~ /^e(\d{8})$/
            @id = $1
            do_edit
          elsif k =~ /^w(\d{8})$/
            @id = $1
            do_write(cgi.params["msg"])
          elsif k =~ /^c$/
            @id = @db.create_id
            do_create
          end
        end
        unless @id
          if @db.empty?
            @id = @db.create_id
            do_create
          else
            @id = @db.random_id
            do_read
          end
        end
      rescue
        print error
      end
    end

    # 新規ページ作成フォームを表示
    def do_create
      print header("New Page"), editform, footer
    end

    # @idで指定したページを表示
    def do_read
      print header(@db.title(@id), :editable), content, footer
    end

    # @idで指定したページの編集フォームを表示
    def do_edit
      print header(@db.title(@id)), editform, footer
    end

    # @idで指定したページにmsgを書き込む
    def do_write(msg)
      @db.set_msg(@id, msg)
      print "Location: #{ABSOLUTE_URL}?#{@id}\n\n"
    end

    # 編集フォーム
    def editform
      escmsg = CGI.escapeHTML(@db.msg(@id))
      <<-"EOD"
<form action="#{ABSOLUTE_URL}" method="post">
<input type="hidden" name="w#{@id}" value="1">
<textarea cols="#{MAX_COLS}" rows="#{MAX_ROWS}" name="msg">#{escmsg}</textarea><br>
<input type="submit" value="Write">
</form>
      EOD
    end

    # 中身
    def content
      body = CGI.escapeHTML(@db.body(@id))
      body.gsub!(/\n/, "<br />")
      "<p>#{body}</p>"
    end

    # ヘッダ
    def header(title, editable=nil)
      edit = editable == :editable ? %Q(<a href="?e#{@id}">Edit</a>) : ''
      create = %Q(<a href="?c">Create</a>)
      random = %Q(<a href="#{ABSOLUTE_URL}">Random</a>)
      <<-"EOD"
Content-type: text/html; charset=#{CHARSET}

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html lang="ja">
<head>
<meta http-equiv="content-type" content="text/html; charset=#{CHARSET}">
<meta http-equiv="content-style-type" content="text/css">
<base href="#{ABSOLUTE_URL}">
<style type="text/css"><!--
body{font-family:Verdana,sans-serif;margin:2% 20% 10% 20%;color:black;background-color:white;}
input{font-family:Verdana,sans-serif;}
#navi{text-align:right;}
p{line-height:150%;}
a{color:gray;background-color:white;text-decoration:none;}
a:hover{text-decoration:underline;color:white;background-color:gray;}
--></style>
<title>#{title} - #{TITLE}</title>
</head>
<body>
<p id="navi">#{edit} #{create} #{random}</p>
<h1>#{title}</h1>
      EOD
    end

    # フッタ
    def footer
      "</div></body></html>"
    end

    # エラー
    def error
      <<-"EOD"
Content-Type: text/html

<html><head><title>Error</title></head><body><h1>Error</h1><pre>
#{$!}
#{$!.class}
#{$@.join("\n")}
</pre>
      EOD
    end
  end
end