# Copyright (C) 2005 Shugo Maeda # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF # SUCH DAMAGE. require "rast" class MLSearchApp DB_PATH = "/home/shugo/ruby-ml-index" MAX_PAGES = 20 ML_OPTIONS = [ ["", "すべて"], ["ruby-list", "ruby-list"], ["ruby-dev", "ruby-dev"] ] SORT_OPTIONS = [ ["score", "スコア順"], ["date", "日付順"], ["x-mail-count", "メール番号順"], ["from", "送信者順"], ["subject", "件名順"], ] SORT_PROPERTIES = ["date", "x-mail-count", "from", "subject"] ORDER_OPTIONS = [ ["asc", "昇順"], ["desc", "降順"], ] NUM_OPTIONS = [10, 20, 30, 50, 100] MESSAGE = < このプログラムは、 全文検索システムRast のデモ用プログラムです。

RastのRubyバインディングを利用していますが、このプログラム自体は Rastの一部ではありませんので、ご注意ください。

EOF CSS = < 検索条件を入力して、「検索」ボタンを押してください

#{MESSAGE} EOF else body = search end html = format_html(form, body) @request.print(html) end private def parse_args @query = @request.param("query").to_s.strip @start = @request.param("start").to_i @num = @request.param("num").to_i if @num < 1 @num = 10 elsif @num > 100 @num = 100 end @ml = @request.param("ml").to_s @sort = @request.param("sort") || "score" @order = @request.param("order") || "desc" end def format_html(form, body) return < ruby ML 検索

ruby ML 検索

#{form} #{body}

Powered by Rast #{Rast::VERSION}

EOF end def format_form num_options = NUM_OPTIONS.collect { |n| if n == @num "" else "" end }.join("\n") ml_options = format_options(ML_OPTIONS, @ml) sort_options = format_options(SORT_OPTIONS, @sort) order_options = format_options(ORDER_OPTIONS, @order) return <

検索方法

ML: 並べ替え: 表示件数:

EOF end def format_options(options, value) return options.collect { |val, label| if val == value "" else "" end }.join("\n") end def search db = Rast::DB.open(@db_path, Rast::DB::RDONLY) begin options = create_search_options query = create_query t = Time.now result = db.search(query, options) secs = Time.now - t return format_result(result, secs) rescue return "

エラー: #{_($!.to_s)}

\n" ensure db.close end end def create_search_options options = { "properties" => [ "x-ml-name", "x-mail-count", "subject", "from", "date" ], "need_summary" => true, "summary_nchars" => 200, "start_no" => @start, "num_items" => @num } if SORT_PROPERTIES.include?(@sort) options["sort_method"] = Rast::SORT_METHOD_PROPERTY options["sort_property"] = @sort end if @order == "asc" options["sort_order"] = Rast::SORT_ORDER_ASCENDING else options["sort_order"] = Rast::SORT_ORDER_DESCENDING end return options end def create_query q = @query if !@ml.empty? q += " x-ml-name = #{@ml}" end return q end def format_result(result, secs) buf = "" buf.concat(< #{_(@query)} の検索結果 #{result.hit_count} 件中 #{@start + 1} - #{@start + result.items.length} 件目 (#{secs} 秒)

EOF for item in result.items buf.concat(format_result_item(result, item)) end if result.hit_count > 0 buf.concat(format_links(result)) end return buf end def format_result_item(result, item) x_ml_name, x_mail_count, subject, from, date = *item.properties summary = item.summary for s in [subject, from, summary] s.gsub!(/([A-Za-z0-9])@[A-Za-z0-9\-.]+/, %q%\\1@...%) end summary = _(summary) for term in result.terms summary.gsub!(Regexp.new(Regexp.quote(term.term), true, "utf-8"), "\\&") end return < #{_(subject)}

送信者: #{_(from)}
日付: #{_(date.tr('T-', ' /'))}

#{summary}

EOF end def format_links(result) page_count = (result.hit_count - 1) / @num + 1 current_page = @start / @num + 1 first_page = current_page - (MAX_PAGES / 2 - 1) if first_page < 1 first_page = 1 end last_page = first_page + MAX_PAGES - 1 if last_page > page_count last_page = page_count end buf = "

\n" if current_page > 1 buf.concat(format_link("前へ", @start - @num, @num)) end if first_page > 1 buf.concat("... ") end for i in first_page..last_page if i == current_page buf.concat("#{i} ") else buf.concat(format_link(i.to_s, (i - 1) * @num, @num)) end end if last_page < page_count buf.concat("... ") end if current_page < page_count buf.concat(format_link("次へ", @start + @num, @num)) end buf.concat("

\n") return buf end def format_link(label, start, num) return format('%s ', _(@request.script_name), escape_url(@query), start, num, _(@ml), _(@sort), _(@order), _(label)) end def _(str) return str.gsub(/&/n, '&').gsub(/\"/n, '"').gsub(/>/n, '>').gsub(/