groonga 0.0.5 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. data/NEWS.ja.rdoc +13 -0
  2. data/NEWS.rdoc +13 -0
  3. data/README.ja.rdoc +2 -1
  4. data/README.rdoc +2 -1
  5. data/Rakefile +0 -1
  6. data/TUTORIAL.ja.rdoc +49 -4
  7. data/benchmark/read-write-many-small-items.rb +5 -4
  8. data/benchmark/write-many-small-items.rb +5 -4
  9. data/example/bookmark.rb +30 -1
  10. data/example/index-html.rb +79 -0
  11. data/example/search/config.ru +182 -0
  12. data/example/search/public/css/groonga.css +122 -0
  13. data/ext/rb-grn-array.c +12 -9
  14. data/ext/rb-grn-column.c +13 -8
  15. data/ext/rb-grn-context.c +34 -16
  16. data/ext/rb-grn-database.c +3 -2
  17. data/ext/rb-grn-expression-builder.c +8 -2
  18. data/ext/rb-grn-expression.c +3 -3
  19. data/ext/rb-grn-hash.c +13 -10
  20. data/ext/rb-grn-object.c +127 -19
  21. data/ext/rb-grn-patricia-trie.c +8 -7
  22. data/ext/rb-grn-table-cursor.c +2 -40
  23. data/ext/rb-grn-table.c +154 -42
  24. data/ext/rb-grn-type.c +18 -10
  25. data/ext/rb-grn-utils.c +22 -20
  26. data/ext/rb-grn.h +9 -12
  27. data/extconf.rb +1 -1
  28. data/html/developer.html +1 -1
  29. data/html/index.html +2 -2
  30. data/lib/groonga/expression-builder.rb +133 -63
  31. data/lib/groonga/schema.rb +229 -37
  32. data/test/groonga-test-utils.rb +1 -1
  33. data/test/test-array.rb +1 -0
  34. data/test/test-context.rb +7 -1
  35. data/test/test-database.rb +11 -3
  36. data/test/test-expression-builder.rb +26 -2
  37. data/test/test-fix-size-column.rb +2 -1
  38. data/test/test-hash.rb +6 -1
  39. data/test/test-record.rb +2 -1
  40. data/test/test-schema.rb +85 -10
  41. data/test/test-table.rb +99 -3
  42. data/test/test-type.rb +3 -2
  43. data/test/test-variable-size-column.rb +2 -1
  44. data/test-unit/Rakefile +6 -1
  45. data/test-unit/lib/test/unit/autorunner.rb +26 -3
  46. data/test-unit/lib/test/unit/priority.rb +21 -1
  47. data/test-unit/lib/test/unit/testcase.rb +101 -36
  48. data/test-unit/lib/test/unit/ui/console/testrunner.rb +7 -4
  49. data/test-unit/test/{test_testcase.rb → test-testcase.rb} +30 -1
  50. data/test-unit/test/test_assertions.rb +1 -1
  51. metadata +9 -6
data/NEWS.ja.rdoc CHANGED
@@ -1,5 +1,18 @@
1
1
  = お知らせ
2
2
 
3
+ == 0.0.6: 2009-07-31
4
+
5
+ * groonga 0.1.1対応
6
+ * ドキュメントの修正 [id:mat_aki]
7
+ * Groonga::Table#selectでのg式対応
8
+ * APIの追加
9
+ * Groonga::Table#union!
10
+ * Groonga::Table#intersect!
11
+ * Groonga::Table#differene!
12
+ * Groonga::Table#merge!
13
+ * tar.gzも提供 [id:m_seki]
14
+ * メモリリークの修正
15
+
3
16
  == 0.0.3: 2009-07-18
4
17
 
5
18
  * [#26145] Groonga::TableKeySupport#has_key?の追加 [Tasuku SUENAGA]
data/NEWS.rdoc CHANGED
@@ -1,5 +1,18 @@
1
1
  = NEWS
2
2
 
3
+ == 0.0.6: 2009-07-31
4
+
5
+ * Supported groonga 0.1.1.
6
+ * Fixed documents [id:mat_aki]
7
+ * Supported groonga expression for searching.
8
+ * Added API
9
+ * Groonga::Table#union!
10
+ * Groonga::Table#intersect!
11
+ * Groonga::Table#differene!
12
+ * Groonga::Table#merge!
13
+ * Provided tar.gz [id:m_seki]
14
+ * Fixed memory leaks
15
+
3
16
  == 0.0.3: 2009-07-18
4
17
 
5
18
  * [#26145] Added Groonga::TableKeySupport#has_key? [Tasuku SUENAGA]
data/README.ja.rdoc CHANGED
@@ -32,7 +32,7 @@ pkg-config.rbはrcairoに付属しているもので、これはRubyライセ
32
32
  == 依存ソフトウェア
33
33
 
34
34
  * Ruby >= 1.8 (1.9.1対応)
35
- * groonga >= 0.0.8
35
+ * groonga >= 0.1.1
36
36
 
37
37
  == インストール
38
38
 
@@ -54,3 +54,4 @@ http://lists.sourceforge.jp/mailman/listinfo/groonga-dev
54
54
  * グニャラくん: バグレポートしてくれました。
55
55
  * にくさん: バグレポートしてくれました。
56
56
  * daraさん: テストを書いてくれました。
57
+ * id:mat_akiさん: チュートリアルのバグを教えてくれました。
data/README.rdoc CHANGED
@@ -33,7 +33,7 @@ license/GPL for details.
33
33
  == Dependencies
34
34
 
35
35
  * Ruby >= 1.8 (including 1.9.1)
36
- * groonga >= 0.0.8
36
+ * groonga >= 0.1.1
37
37
 
38
38
  == Install
39
39
 
@@ -55,3 +55,4 @@ http://rubyforge.org/mailman/listinfo/groonga-users-en
55
55
  * Tasuku SUENAGA: sent bug reports.
56
56
  * niku: sent bug reports.
57
57
  * dara: wrote tests.
58
+ * id:mat_aki: sent bug reports.
data/Rakefile CHANGED
@@ -119,7 +119,6 @@ Hoe.spec('groonga') do |_project|
119
119
  description = cleanup_white_space(entries[entries.index("Description") + 1])
120
120
  project.summary, project.description, = description.split(/\n\n+/, 3)
121
121
 
122
- project.need_tar = false
123
122
  project.remote_rdoc_dir = "groonga"
124
123
  end
125
124
 
data/TUTORIAL.ja.rdoc CHANGED
@@ -187,9 +187,9 @@ http://qwik.jp/senna/senna2.files/rect4605.png
187
187
 
188
188
  次に、<tt><comments></tt>テーブルを追加します。
189
189
 
190
- >> comments = Groonga::Hash.create(:name => "<comments>",
191
- :key_type => "<shorttext>")
192
- => #<Groonga::Hash ...>
190
+ >> comments = Groonga::Array.create(:name => "<comments>",
191
+ :key_type => "<shorttext>")
192
+ => #<Groonga::Array ...>
193
193
  >> comments.define_column("item", items)
194
194
  => #<Groonga::FixSizeColumn ..>
195
195
  >> comments.define_column("author", users)
@@ -347,4 +347,49 @@ http://qwik.jp/senna/senna2.files/rect4605.png
347
347
 
348
348
  == 少し複雑な検索
349
349
 
350
- つづく。。。
350
+ ↓はまだ動かない!!!
351
+
352
+ さらに実用的な検索について考えてみましょう。
353
+
354
+ ブックマークが大量に蓄積されるに従って、より的確に適合度を算
355
+ 出する必要性に迫られます。
356
+
357
+ 今のところ検索対象として利用できるのは<tt><items>.title</tt>
358
+ と<tt><comments>.content</tt>ですが、<tt><items>.title</tt>は
359
+ 元ページから得られるやや信頼できる情報なのに対して、
360
+ <tt><comments>.content</tt>はブックマークユーザが任意に設定で
361
+ きる情報で、やや信憑性に乏しいと言えます。しかし、再現率を確
362
+ 保するためにはユーザのコメントも是非対象に含めたいところです。
363
+
364
+ そこで、以下のようなポリシーで検索を行うことにします。
365
+
366
+ * <tt><items>.title</tt>か<tt><comments>.content</tt>のいずれ
367
+ かにマッチするitemを検索する。
368
+ * ただし、<tt><items>.title</tt>にマッチしたレコードはスコア
369
+ を10倍重み付けする。
370
+ * 同一のitemに対して、キーワードにマッチする<tt>comment</tt>
371
+ が複数存在した場合は、それぞれの<tt>comment</tt>のスコアの
372
+ 和を、該当するitemのスコアとする。
373
+
374
+ 以下のようにして、commentとitemとそれぞれに対する検索結果を求
375
+ めます。
376
+
377
+ >> ruby_comments = @comments.select {|record| record["content"] =~ "Ruby"}
378
+ #<Groonga::Hash ..., size: <2>>
379
+ >> ruby_items = @items.select("*W1:50 title:%Ruby")
380
+ #<Groonga::Hash ..., size: <2>>
381
+
382
+ _ruby_comments_の結果をitem毎にグループ化し、_ruby_items_と
383
+ unionして出力します。
384
+
385
+ >> ruby_items = ruby_comments.group([".item"]).union!(ruby_items)
386
+ #<Groonga::Hash ..., size: <4>>
387
+ >> ruby_items.sort([{:key => ".:score", :order => "descendant"}]).each do |record|
388
+ >> p [record[".:score"], record[".title"]]
389
+ >> end
390
+ [1, "るびま"]
391
+ [1, "オブジェクトスクリプト言語Ruby"]
392
+ [1, "Ruby"]
393
+ [1, "ラングバ"]
394
+
395
+ これで目的の結果が得られました。(FIXME: 得られていない!)
@@ -55,10 +55,11 @@ begin
55
55
 
56
56
  require 'groonga'
57
57
  @database = Groonga::Database.create
58
+ value_type = Groonga::Type.new("Text8", :size => 8)
58
59
 
59
60
  item("groonga: Hash: memory") do
60
61
  @hash = Groonga::Hash.create(:key_type => "<shorttext>",
61
- :value_size => 8)
62
+ :value_type => value_type)
62
63
  values.each do |value|
63
64
  @hash[value] = value
64
65
  @hash[value]
@@ -67,7 +68,7 @@ begin
67
68
 
68
69
  item("groonga: Trie: memory") do
69
70
  @hash = Groonga::PatriciaTrie.create(:key_type => "<shorttext>",
70
- :value_size => 8)
71
+ :value_type => value_type)
71
72
  values.each do |value|
72
73
  @hash[value] = value
73
74
  @hash[value]
@@ -77,7 +78,7 @@ begin
77
78
  @hash_file = Tempfile.new("groonga-hash")
78
79
  item("groonga: Hash: file") do
79
80
  @hash = Groonga::Hash.create(:key_type => "<shorttext>",
80
- :value_size => 8,
81
+ :value_type => value_type,
81
82
  :path => @hash_file.path)
82
83
  values.each do |value|
83
84
  @hash[value] = value
@@ -88,7 +89,7 @@ begin
88
89
  trie_file = Tempfile.new("groonga-trie")
89
90
  item("groonga: Trie: file") do
90
91
  @hash = Groonga::PatriciaTrie.create(:key_type => "<shorttext>",
91
- :value_size => 8,
92
+ :value_type => value_type,
92
93
  :path => trie_file.path)
93
94
  values.each do |value|
94
95
  @hash[value] = value
@@ -54,10 +54,11 @@ begin
54
54
 
55
55
  require 'groonga'
56
56
  @database = Groonga::Database.create
57
+ value_type = Groonga::Type.new("Text8", :size => 8)
57
58
 
58
59
  item("groonga: Hash: memory") do
59
60
  @hash = Groonga::Hash.create(:key_type => "<shorttext>",
60
- :value_size => 8)
61
+ :value_type => value_type)
61
62
  values.each do |value|
62
63
  @hash[value] = value
63
64
  end
@@ -65,7 +66,7 @@ begin
65
66
 
66
67
  item("groonga: Trie: memory") do
67
68
  @hash = Groonga::PatriciaTrie.create(:key_type => "<shorttext>",
68
- :value_size => 8)
69
+ :value_type => value_type)
69
70
  values.each do |value|
70
71
  @hash[value] = value
71
72
  end
@@ -74,7 +75,7 @@ begin
74
75
  @hash_file = Tempfile.new("groonga-hash")
75
76
  item("groonga: Hash: file") do
76
77
  @hash = Groonga::Hash.create(:key_type => "<shorttext>",
77
- :value_size => 8,
78
+ :value_type => value_type,
78
79
  :path => @hash_file.path)
79
80
  values.each do |value|
80
81
  @hash[value] = value
@@ -84,7 +85,7 @@ begin
84
85
  trie_file = Tempfile.new("groonga-trie")
85
86
  item("groonga: Trie: file") do
86
87
  @hash = Groonga::PatriciaTrie.create(:key_type => "<shorttext>",
87
- :value_size => 8,
88
+ :value_type => value_type,
88
89
  :path => trie_file.path)
89
90
  values.each do |value|
90
91
  @hash[value] = value
data/example/bookmark.rb CHANGED
@@ -14,6 +14,12 @@ rescue LoadError
14
14
  require "groonga"
15
15
  end
16
16
 
17
+ require 'time'
18
+
19
+ # Groonga::Logger.register(:level => :debug) do |level, time, title, message, location|
20
+ # p [level, time, title, message, location]
21
+ # end
22
+
17
23
  $KCODE = "UTF-8"
18
24
  Groonga::Context.default_options = {:encoding => :utf8}
19
25
 
@@ -43,10 +49,13 @@ p(terms = Groonga::Hash.create(:name => "<terms>",
43
49
  :default_tokenizer => "<token:bigram>"))
44
50
  p terms.define_index_column("item_title", items,
45
51
  :persistent => persistent,
52
+ :with_weight => true,
53
+ :with_section => true,
54
+ :with_position => true,
46
55
  :source => "<items>.title")
47
56
 
48
57
  p items.find("http://ja.wikipedia.org/wiki/Ruby")["title"] = "Ruby"
49
- p items.find("http://www.ruby-lang.org/")["title"] = "オブジェクトスクリプト言語Ruby"
58
+ p items.find("http://www.ruby-lang.org/")["title"] = "オブジェクト指向スクリプト言語Ruby"
50
59
 
51
60
  p(users = Groonga::Hash.create(:name => "<users>",
52
61
  :key_type => "<shorttext>",
@@ -63,6 +72,9 @@ p comments.define_column("issued", "<time>")
63
72
 
64
73
  p terms.define_index_column("comment_content", comments,
65
74
  :persistent => persistent,
75
+ :with_weight => true,
76
+ :with_section => true,
77
+ :with_position => true,
66
78
  :source => "<comments>.content")
67
79
 
68
80
  p users.add("moritan", :name => "モリタン")
@@ -96,6 +108,15 @@ p add_bookmark("http://d.hatena.ne.jp/higepon/20070815/1187192864",
96
108
  p add_bookmark("http://practical-scheme.net/docs/cont-j.html",
97
109
  "なんでも継続", "taporobo", "トランポリン LISP continuation",
98
110
  1187568692)
111
+ p add_bookmark("http://jp.rubyist.net/managinze",
112
+ "るびま", "moritan", "Ruby ドキュメント",
113
+ Time.now)
114
+ p add_bookmark("http://jp.rubyist.net/managinze",
115
+ "るびま", "taporobo", "Ruby 雑誌",
116
+ Time.now)
117
+ p add_bookmark("http://groonga.rubyforge.org/",
118
+ "ラングバ", "moritan", "Ruby groonga",
119
+ Time.parse("2009-07-19"))
99
120
 
100
121
 
101
122
  records = comments.select do |record|
@@ -128,3 +149,11 @@ records.group([".item"]).each do |record|
128
149
  item.key,
129
150
  item[".title"]]
130
151
  end
152
+
153
+ p ruby_comments = @comments.select {|record| record["content"] =~ "Ruby"}
154
+ p ruby_items = @items.select("*W1:50 title:%Ruby")
155
+
156
+ p ruby_items = ruby_comments.group([".item"]).union!(ruby_items)
157
+ ruby_items.sort([{:key => ".:score", :order => "descending"}]).each do |record|
158
+ p [record[".:score"], record[".title"]]
159
+ end
@@ -0,0 +1,79 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ if ARGV.size < 2 or ARGV.find {|option| option == "-h" or option == "--help"}
4
+ puts "Usage: #{$0} DATABASE_FILE [FILE_OR_DIRECTORY ...]"
5
+ exit
6
+ end
7
+
8
+ require 'pathname'
9
+
10
+ base_directory = Pathname(__FILE__).dirname + ".."
11
+ $LOAD_PATH.unshift((base_directory + "ext").to_s)
12
+ $LOAD_PATH.unshift((base_directory + "lib").to_s)
13
+
14
+ require 'rubygems'
15
+ require 'groonga'
16
+ require 'nokogiri'
17
+
18
+ database_file, *targets = ARGV
19
+
20
+ database_file = Pathname(database_file)
21
+ database_directory = database_file.dirname
22
+ database_directory.mkpath unless database_directory.exist?
23
+
24
+ if database_file.exist?
25
+ Groonga::Database.open(database_file.to_s)
26
+ else
27
+ Groonga::Database.create(:path => database_file.to_s)
28
+ Groonga::Schema.define do |schema|
29
+ schema.create_table("documents") do |table|
30
+ table.string("title")
31
+ table.text("content")
32
+ table.string("path")
33
+ table.time("last-modified")
34
+ end
35
+
36
+ schema.create_table("terms",
37
+ :type => :patricia_trie,
38
+ :key_normalize => true,
39
+ :default_tokenizer => "TokenBigram") do |table|
40
+ table.index("documents.title")
41
+ table.index("documents.content")
42
+ end
43
+ end
44
+ end
45
+
46
+ documents = Groonga::Context.default["documents"]
47
+
48
+ targets.each do |target|
49
+ target = Pathname(target)
50
+ target.find do |path|
51
+ throw :prune if path.basename.to_s == ".svn"
52
+ if path.file? and path.extname == ".html"
53
+ path.open do |html|
54
+ values = {:path => path.relative_path_from(target).to_s}
55
+ _documents = documents.select do |record|
56
+ record["path"] == values[:path]
57
+ end
58
+ if _documents.size.zero?
59
+ document = documents.add
60
+ else
61
+ document = _documents.to_a[0].key
62
+ end
63
+
64
+ html_document = Nokogiri::HTML(html)
65
+ html_document.css("title").each do |title|
66
+ values[:title] = title.text
67
+ end
68
+ html_document.css("body").each do |body|
69
+ values[:content] = body.text
70
+ end
71
+ values["last-modified"] = path.mtime
72
+
73
+ values.each do |key, value|
74
+ document[key] = value
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,182 @@
1
+ # -*- mode: ruby; coding: utf-8 -*-
2
+
3
+ base_dir = File.join(File.dirname(__FILE__), "..", "..")
4
+ $LOAD_PATH.unshift(File.join(base_dir, "ext"))
5
+ $LOAD_PATH.unshift(File.join(base_dir, "lib"))
6
+
7
+ require 'rubygems'
8
+ require 'rack'
9
+ require 'groonga'
10
+
11
+ use Rack::CommonLogger
12
+ use Rack::Static, :urls => ["/css", "/images"], :root => "public"
13
+
14
+ Groonga::Database.new("data/database")
15
+
16
+ class Searcher
17
+ include Rack::Utils
18
+
19
+ def initialize
20
+ @documents = Groonga::Context.default["documents"]
21
+ end
22
+
23
+ def call(env)
24
+ request = Rack::Request.new(env)
25
+ response = Rack::Response.new
26
+ response["Content-Type"] = "text/html"
27
+
28
+ response.write(<<-EOH)
29
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
30
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
31
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja">
32
+ <head>
33
+ <meta http-equiv="content-type" content="text/html;charset=UTF-8" />
34
+ <meta name="robot" content="noindex,nofollow" />
35
+ <title>groongaで全文検索</title>
36
+ <link rel="stylesheet" href="css/groonga.css" type="text/css" media="all" />
37
+ </head>
38
+ <body>
39
+ <div class="header">
40
+ <h1>groongaで全文検索</h1>
41
+ </div>
42
+
43
+ <div class="content">
44
+ EOH
45
+
46
+ render_search_box(request, response)
47
+ render_search_result(request, response)
48
+
49
+ response.write(<<-EOF)
50
+ </div>
51
+
52
+ <div class="footer">
53
+ <p class="powered-by-groonga">
54
+ <a href="http://groonga.org/">groonga</a>
55
+ #{Groonga::VERSION.join('.')}を使っています。
56
+ </p>
57
+ <p class="powered-by-ruby-groonga">
58
+ <a href="http://groonga.rubyforge.org/">Ruby/groonga</a>
59
+ #{Groonga::BINDINGS_VERSION.join('.')}を使っています。
60
+ </p>
61
+ </div>
62
+
63
+ </body>
64
+ </html>
65
+ EOF
66
+ response.to_a
67
+ end
68
+
69
+ private
70
+ def query(request)
71
+ request['query'] || ''
72
+ end
73
+
74
+ def words(request)
75
+ query(request).split
76
+ end
77
+
78
+ def render_search_box(request, response)
79
+ response.write(<<-EOF)
80
+ <form method="get" action="./">
81
+ <p>
82
+ <a href="."><img src="images/mini-groonga.png" alt="groonga" /></a>
83
+ <input name="query" type="text" value="#{escape_html(query(request))}" />
84
+ <input type="submit" value="検索" />
85
+ </p>
86
+ </form>
87
+ EOF
88
+ end
89
+
90
+ def render_search_result(request, response)
91
+ _words = words(request)
92
+ if _words.empty?
93
+ records = []
94
+ response.write(<<-EOS)
95
+ <div class='search-summary'>
96
+ <p>Rubyでgroonga使って全文検索</p>
97
+ </div>
98
+ EOS
99
+ else
100
+ offset = 0
101
+ options = {}
102
+ before = Time.now
103
+ records = @documents.select do |record|
104
+ expression = nil
105
+ _words.each do |word|
106
+ sub_expression = record["content"] =~ word
107
+ if expression.nil?
108
+ expression = sub_expression
109
+ else
110
+ expression &= sub_expression
111
+ end
112
+ end
113
+ expression
114
+ end
115
+ total_records = records.size
116
+ records = records.sort([[".:score", "descending"],
117
+ [".last-modified", "descending"]],
118
+ :limit => 20)
119
+ elapsed = Time.now - before
120
+
121
+ response.write(<<-EOS)
122
+ <div class='search-summary'>
123
+ <p>
124
+ <span class="keyword">#{escape_html(query(request))}</span>の検索結果:
125
+ <span class="total-entries">#{total_records}</span>件中
126
+ <span class="display-range">
127
+ #{total_records.zero? ? 0 : offset + 1}
128
+ -
129
+ #{offset + records.size}
130
+ </span>
131
+ 件(#{elapsed}秒)
132
+ </p>
133
+ </div>
134
+ EOS
135
+ end
136
+
137
+ response.write(" <div class='records'>\n")
138
+ records.each do |record|
139
+ render_record(request, response, record)
140
+ end
141
+ response.write(" </div>\n")
142
+ end
143
+
144
+ def render_record(request, response, record)
145
+ response.write(" <div class='record'>\n")
146
+ href = escape_html(record['.path'])
147
+ title = escape_html(record['.title'])
148
+ last_modified = escape_html(record['.last-modified'].iso8601)
149
+ score = record.score
150
+ response.write(" <h2><a href='#{href}'>#{title}</a>(#{score})</h2>\n")
151
+ render_snippet(request, response, record)
152
+ response.write(<<-EOM)
153
+ <p class="metadata">
154
+ <span class="url">#{unescape(href)}</span>
155
+ -
156
+ <span class="last-modified">#{last_modified}</span>
157
+ </p>
158
+ EOM
159
+ response.write(" </div>\n")
160
+ end
161
+
162
+ def render_snippet(request, response, record)
163
+ open_tag = "<span class=\"keyword\">"
164
+ close_tag = "</span>"
165
+ snippet = Groonga::Snippet.new(:width => 100,
166
+ :default_open_tag => open_tag,
167
+ :default_close_tag => close_tag,
168
+ :html_escape => true,
169
+ :normalize => true)
170
+ words(request).each do |word|
171
+ snippet.add_keyword(word)
172
+ end
173
+ separator = "\n<span class='separator'>...</span>\n"
174
+ response.write(<<-EOS)
175
+ <p class="snippet">
176
+ #{snippet.execute(record[".content"]).join(separator)}
177
+ </p>
178
+ EOS
179
+ end
180
+ end
181
+
182
+ run Searcher.new
@@ -0,0 +1,122 @@
1
+ body
2
+ {
3
+ background-color: #fff;
4
+ color: #000;
5
+ }
6
+
7
+ div.header
8
+ {
9
+ display: none;
10
+ }
11
+
12
+ div.search-box
13
+ {
14
+ width: 100%;
15
+ text-align: center;
16
+ }
17
+
18
+ div.re-search
19
+ {
20
+ }
21
+
22
+ div.search-summary
23
+ {
24
+ font-size: small;
25
+ border-top: 1px solid #fc3;
26
+ background-color: #ffd;
27
+ }
28
+
29
+ div.search-summary p
30
+ {
31
+ padding: 3px;
32
+ margin: 0;
33
+ }
34
+
35
+ div.records
36
+ {
37
+ font-size: smaller;
38
+ padding-top: 10px;
39
+ padding-left: 30px;
40
+ margin-right: 20%;
41
+ margin-bottom: 30px;
42
+ }
43
+
44
+ div.records h2
45
+ {
46
+ padding: 0;
47
+ margin-bottom: 5px;
48
+ }
49
+
50
+ div.records p
51
+ {
52
+ }
53
+
54
+ p.snippet
55
+ {
56
+ padding: 0;
57
+ margin: 0;
58
+ }
59
+
60
+ p.snippet span.separator
61
+ {
62
+ font-weight: bold;
63
+ }
64
+
65
+ span.keyword,
66
+ span.total-entries,
67
+ span.display-range
68
+ {
69
+ font-weight: bold;
70
+ }
71
+
72
+ div.pagination
73
+ {
74
+ text-align: center;
75
+ margin-bottom: 20px;
76
+ }
77
+
78
+ p.metadata
79
+ {
80
+ padding: 0;
81
+ margin: 0;
82
+ margin-top: 5px;
83
+ }
84
+
85
+ span.url
86
+ {
87
+ color: green;
88
+ }
89
+
90
+ pre
91
+ {
92
+ overflow: auto;
93
+ margin: 20px 30px;
94
+ padding: 10px;
95
+ border-top: 1px solid #fc3;
96
+ background-color: #ffd;
97
+ }
98
+
99
+ a img
100
+ {
101
+ border-style: none;
102
+ }
103
+
104
+ ul.sources li
105
+ {
106
+ display: inline;
107
+ }
108
+
109
+ div.footer
110
+ {
111
+ border-top: 1px solid #fc3;
112
+ background-color: #ffd;
113
+ margin-top: 5px;
114
+ text-align: right;
115
+ }
116
+
117
+ div.footer p
118
+ {
119
+ font-size: small;
120
+ padding: 0;
121
+ margin: 0;
122
+ }