groonga 0.0.5 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/NEWS.ja.rdoc +13 -0
- data/NEWS.rdoc +13 -0
- data/README.ja.rdoc +2 -1
- data/README.rdoc +2 -1
- data/Rakefile +0 -1
- data/TUTORIAL.ja.rdoc +49 -4
- data/benchmark/read-write-many-small-items.rb +5 -4
- data/benchmark/write-many-small-items.rb +5 -4
- data/example/bookmark.rb +30 -1
- data/example/index-html.rb +79 -0
- data/example/search/config.ru +182 -0
- data/example/search/public/css/groonga.css +122 -0
- data/ext/rb-grn-array.c +12 -9
- data/ext/rb-grn-column.c +13 -8
- data/ext/rb-grn-context.c +34 -16
- data/ext/rb-grn-database.c +3 -2
- data/ext/rb-grn-expression-builder.c +8 -2
- data/ext/rb-grn-expression.c +3 -3
- data/ext/rb-grn-hash.c +13 -10
- data/ext/rb-grn-object.c +127 -19
- data/ext/rb-grn-patricia-trie.c +8 -7
- data/ext/rb-grn-table-cursor.c +2 -40
- data/ext/rb-grn-table.c +154 -42
- data/ext/rb-grn-type.c +18 -10
- data/ext/rb-grn-utils.c +22 -20
- data/ext/rb-grn.h +9 -12
- data/extconf.rb +1 -1
- data/html/developer.html +1 -1
- data/html/index.html +2 -2
- data/lib/groonga/expression-builder.rb +133 -63
- data/lib/groonga/schema.rb +229 -37
- data/test/groonga-test-utils.rb +1 -1
- data/test/test-array.rb +1 -0
- data/test/test-context.rb +7 -1
- data/test/test-database.rb +11 -3
- data/test/test-expression-builder.rb +26 -2
- data/test/test-fix-size-column.rb +2 -1
- data/test/test-hash.rb +6 -1
- data/test/test-record.rb +2 -1
- data/test/test-schema.rb +85 -10
- data/test/test-table.rb +99 -3
- data/test/test-type.rb +3 -2
- data/test/test-variable-size-column.rb +2 -1
- data/test-unit/Rakefile +6 -1
- data/test-unit/lib/test/unit/autorunner.rb +26 -3
- data/test-unit/lib/test/unit/priority.rb +21 -1
- data/test-unit/lib/test/unit/testcase.rb +101 -36
- data/test-unit/lib/test/unit/ui/console/testrunner.rb +7 -4
- data/test-unit/test/{test_testcase.rb → test-testcase.rb} +30 -1
- data/test-unit/test/test_assertions.rb +1 -1
- 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.
|
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.
|
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::
|
191
|
-
|
192
|
-
=> #<Groonga::
|
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
|
-
:
|
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
|
-
:
|
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
|
-
:
|
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
|
-
:
|
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
|
-
:
|
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
|
-
:
|
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
|
-
:
|
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
|
-
:
|
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"] = "
|
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
|
+
}
|