xapian-fu 1.3.2 → 1.4.0
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/CHANGELOG.rdoc +13 -0
- data/README.rdoc +67 -4
- data/lib/xapian_fu/query_parser.rb +32 -8
- data/lib/xapian_fu/result_set.rb +11 -0
- data/lib/xapian_fu/version.rb +1 -1
- data/lib/xapian_fu/xapian_db.rb +98 -11
- data/lib/xapian_fu/xapian_doc.rb +40 -24
- data/lib/xapian_fu/xapian_doc_value_accessor.rb +20 -25
- data/spec/facets_spec.rb +34 -0
- data/spec/query_parser_spec.rb +7 -0
- data/spec/xapian_db_spec.rb +134 -0
- data/spec/xapian_doc_spec.rb +24 -0
- data/spec/xapian_doc_value_accessor_spec.rb +11 -4
- metadata +8 -5
data/CHANGELOG.rdoc
CHANGED
@@ -1,3 +1,16 @@
|
|
1
|
+
=== 1.4.0 (13th March 2012)
|
2
|
+
|
3
|
+
* Support for indexing Arrays properly
|
4
|
+
|
5
|
+
* Support returning all and no documents by using Xapian's special
|
6
|
+
queries `MatchAll` and `MatchNothing`.
|
7
|
+
|
8
|
+
* Add boolean terms and faceted queries.
|
9
|
+
|
10
|
+
See [http://bit.ly/rMuA4M](http://bit.ly/rMuA4M).
|
11
|
+
|
12
|
+
* Fix number range queries when no prefixes are given.
|
13
|
+
|
1
14
|
=== 1.3.2 (4th December 2011)
|
2
15
|
|
3
16
|
* Number range queries (Damian Janowski)
|
data/README.rdoc
CHANGED
@@ -17,6 +17,14 @@ Hash with full text indexing (and ACID transactions).
|
|
17
17
|
|
18
18
|
sudo gem install xapian-fu
|
19
19
|
|
20
|
+
=== Xapian Bindings
|
21
|
+
|
22
|
+
Xapian Fu requires the Xapian Ruby bindings to be available. On
|
23
|
+
Debian/Ubuntu, you can install the `libxapian-ruby1.8` package to get
|
24
|
+
them. Alternatively, you can install the `xapian-ruby` gem, which
|
25
|
+
reportedly will also provide them. You can also just get the
|
26
|
+
upstream Xapian release and manually install it.
|
27
|
+
|
20
28
|
== Documentation
|
21
29
|
|
22
30
|
XapianFu::XapianDb is the corner-stone of XapianFu. A XapianDb
|
@@ -97,7 +105,7 @@ type of object is instantiated when returning those stored values.
|
|
97
105
|
And in the case of Fixnum and Bignum, allows you to order search
|
98
106
|
results without worrying about leading zeros.
|
99
107
|
|
100
|
-
db = XapianDb.new(:fields => {
|
108
|
+
db = XapianDb.new(:fields => {
|
101
109
|
:title => { :store => true },
|
102
110
|
:released => { :type => Date, :store => true },
|
103
111
|
:votes => { :type => Fixnum, :store => true }
|
@@ -113,6 +121,13 @@ Find the document with the highest :year value
|
|
113
121
|
|
114
122
|
db.documents.max(:year)
|
115
123
|
|
124
|
+
== Special queries
|
125
|
+
|
126
|
+
XapianFu supports Xapian's `MatchAll` and `MatchNothing` queries:
|
127
|
+
|
128
|
+
db.search(:all)
|
129
|
+
db.search(:nothing)
|
130
|
+
|
116
131
|
== Search examples
|
117
132
|
|
118
133
|
Search on particular fields
|
@@ -145,6 +160,54 @@ And any combinations of the above:
|
|
145
160
|
|
146
161
|
db.search("(ruby OR sinatra) -rails xap*")
|
147
162
|
|
163
|
+
== Boolean terms
|
164
|
+
|
165
|
+
If you want to implement something like [this](http://getting-started-with-xapian.readthedocs.org/en/latest/howtos/boolean_filters.html#searching),
|
166
|
+
then:
|
167
|
+
|
168
|
+
db = XapianDb.new(
|
169
|
+
fields: {
|
170
|
+
name: {:index => true},
|
171
|
+
colors: {:boolean => true}
|
172
|
+
}
|
173
|
+
)
|
174
|
+
|
175
|
+
db << {name: "Foo", colors: ["red", "black"]}
|
176
|
+
db << {name: "Foo", colors: ["red", "green"]}
|
177
|
+
db << {name: "Foo", colors: ["blue", "yellow"]}
|
178
|
+
|
179
|
+
db.search("foo", filter: {:colors => ["red"]})
|
180
|
+
|
181
|
+
The main thing here is that filtering by color doesn't affect the relevancy of the documents returned.
|
182
|
+
|
183
|
+
== Facets
|
184
|
+
|
185
|
+
Many times you want to allow users to narrow down the search results by restricting the query
|
186
|
+
to specific values of a given category. This is called [faceted search](readthedocs.org/docs/getting-started-with-xapian/en/latest/xapian-core-rst/facets.html).
|
187
|
+
|
188
|
+
To find out which values you can display to your users, you can do something like this:
|
189
|
+
|
190
|
+
results = db.search("foo", facets: [:colors, :year])
|
191
|
+
|
192
|
+
results.facets
|
193
|
+
# {
|
194
|
+
# :colors => [
|
195
|
+
# ["blue", 4]
|
196
|
+
# ["red", 1]
|
197
|
+
# ],
|
198
|
+
#
|
199
|
+
# :year => [
|
200
|
+
# [2010, 3],
|
201
|
+
# [2011, 2],
|
202
|
+
# [2012, 1]
|
203
|
+
# ]
|
204
|
+
# }
|
205
|
+
|
206
|
+
When filtering by one of these values, it's best to define the field as
|
207
|
+
boolean (see section above) and then use `:filter`:
|
208
|
+
|
209
|
+
db.search("foo", filter: {colors: ["blue"], year: [2010]})
|
210
|
+
|
148
211
|
== ActiveRecord Integration
|
149
212
|
|
150
213
|
XapianFu always stores the :id field, so you can easily use it with
|
@@ -176,7 +239,7 @@ perhaps by reindexing once in a while.
|
|
176
239
|
|
177
240
|
db = XapianDb.new(:dir => 'posts.db')
|
178
241
|
deleted_posts = Post.find(:all, :conditions => 'deleted_at is not null')
|
179
|
-
deleted_posts.each do |post|
|
242
|
+
deleted_posts.each do |post|
|
180
243
|
db.documents.delete(post.id)
|
181
244
|
post.destroy
|
182
245
|
end
|
@@ -184,8 +247,8 @@ perhaps by reindexing once in a while.
|
|
184
247
|
= More Info
|
185
248
|
|
186
249
|
Author:: John Leach (mailto:john@johnleach.co.uk)
|
187
|
-
Copyright:: Copyright (c) 2009-
|
250
|
+
Copyright:: Copyright (c) 2009-2012 John Leach
|
188
251
|
License:: MIT (The Xapian library is GPL)
|
189
252
|
Mailing list:: http://rubyforge.org/mailman/listinfo/xapian-fu-discuss
|
190
253
|
Web page:: http://johnleach.co.uk/documents/xapian-fu
|
191
|
-
Github:: http://github.com/johnl/xapian-fu/tree/master
|
254
|
+
Github:: http://github.com/johnl/xapian-fu/tree/master
|
@@ -49,15 +49,15 @@ module XapianFu #:nodoc:
|
|
49
49
|
# or false.
|
50
50
|
#
|
51
51
|
class QueryParser #:notnew:
|
52
|
-
|
52
|
+
|
53
53
|
# The stemming strategy to use when generating terms from a query.
|
54
54
|
# Defaults to <tt>:some</tt>
|
55
55
|
attr_accessor :stemming_strategy
|
56
|
-
|
56
|
+
|
57
57
|
# The default operation when combining search terms. Defaults to
|
58
58
|
# <tt>:and</tt>
|
59
59
|
attr_accessor :default_op
|
60
|
-
|
60
|
+
|
61
61
|
# The database that this query is agains, used for setting up
|
62
62
|
# fields, stemming, stopping and spelling.
|
63
63
|
attr_accessor :database
|
@@ -72,9 +72,17 @@ module XapianFu #:nodoc:
|
|
72
72
|
self.database = @options[:database]
|
73
73
|
end
|
74
74
|
|
75
|
-
# Parse the given query
|
75
|
+
# Parse the given query and return a Xapian::Query object
|
76
|
+
# Accepts either a string or a special query
|
76
77
|
def parse_query(q)
|
77
|
-
|
78
|
+
case q
|
79
|
+
when :all
|
80
|
+
Xapian::Query.new("")
|
81
|
+
when :nothing
|
82
|
+
Xapian::Query.new()
|
83
|
+
else
|
84
|
+
query_parser.parse_query(q, xapian_flags)
|
85
|
+
end
|
78
86
|
end
|
79
87
|
|
80
88
|
# Return the query string with any spelling corrections made
|
@@ -93,16 +101,32 @@ module XapianFu #:nodoc:
|
|
93
101
|
qp.stemmer = database.stemmer if database
|
94
102
|
qp.default_op = xapian_default_op
|
95
103
|
qp.stemming_strategy = xapian_stemming_strategy
|
104
|
+
|
96
105
|
fields.each do |name, type|
|
106
|
+
next if database && database.boolean_fields.include?(name)
|
97
107
|
qp.add_prefix(name.to_s.downcase, "X" + name.to_s.upcase)
|
98
108
|
end
|
99
109
|
|
110
|
+
database.boolean_fields.each do |name|
|
111
|
+
qp.add_boolean_prefix(name.to_s.downcase, "X#{name.to_s.upcase}")
|
112
|
+
end if database
|
113
|
+
|
100
114
|
database.sortable_fields.each do |field, opts|
|
101
|
-
|
102
|
-
|
115
|
+
prefix, string = nil
|
116
|
+
|
117
|
+
if opts[:range_postfix]
|
118
|
+
prefix = false
|
119
|
+
string = opts[:range_postfix]
|
103
120
|
else
|
104
|
-
|
121
|
+
prefix = true
|
122
|
+
string = opts[:range_prefix] || "#{field.to_s.downcase}:"
|
105
123
|
end
|
124
|
+
|
125
|
+
qp.add_valuerangeprocessor(Xapian::NumberValueRangeProcessor.new(
|
126
|
+
XapianDocValueAccessor.value_key(field),
|
127
|
+
string,
|
128
|
+
prefix
|
129
|
+
))
|
106
130
|
end if database
|
107
131
|
|
108
132
|
@query_parser = qp
|
data/lib/xapian_fu/result_set.rb
CHANGED
@@ -12,12 +12,23 @@ module XapianFu
|
|
12
12
|
# by :corrected_query, otherwise this is empty.
|
13
13
|
attr_reader :corrected_query
|
14
14
|
|
15
|
+
attr_reader :facets
|
16
|
+
|
15
17
|
# nodoc
|
16
18
|
def initialize(options = { })
|
17
19
|
@mset = options[:mset]
|
18
20
|
@current_page = options[:current_page]
|
19
21
|
@per_page = options[:per_page]
|
20
22
|
@corrected_query = options[:corrected_query]
|
23
|
+
@facets = {}
|
24
|
+
@db = options[:xapian_db]
|
25
|
+
|
26
|
+
options[:spies].each do |name, spy|
|
27
|
+
@facets[name] = spy.values.map do |value|
|
28
|
+
[@db.unserialize_value(name, value.term), value.termfreq]
|
29
|
+
end
|
30
|
+
end if options[:spies]
|
31
|
+
|
21
32
|
doc_options = {:xapian_db => options[:xapian_db] }
|
22
33
|
concat mset.matches.collect { |m| XapianDoc.new(m, doc_options) }
|
23
34
|
end
|
data/lib/xapian_fu/version.rb
CHANGED
data/lib/xapian_fu/xapian_db.rb
CHANGED
@@ -100,7 +100,7 @@ module XapianFu #:nodoc:
|
|
100
100
|
#
|
101
101
|
class XapianDb # :nonew:
|
102
102
|
# Path to the on-disk database. Nil if in-memory database
|
103
|
-
attr_reader :dir
|
103
|
+
attr_reader :dir
|
104
104
|
attr_reader :db_flag #:nodoc:
|
105
105
|
# An array of the fields that will be stored in the Xapian
|
106
106
|
attr_reader :store_values
|
@@ -112,6 +112,8 @@ module XapianFu #:nodoc:
|
|
112
112
|
attr_reader :fields
|
113
113
|
# An array of fields that will not be indexed
|
114
114
|
attr_reader :unindexed_fields
|
115
|
+
# An array of fields that will be treated as boolean terms
|
116
|
+
attr_reader :boolean_fields
|
115
117
|
# Whether this db will generate a spelling dictionary during indexing
|
116
118
|
attr_reader :spelling
|
117
119
|
attr_reader :sortable_fields
|
@@ -130,7 +132,7 @@ module XapianFu #:nodoc:
|
|
130
132
|
setup_fields(@options[:fields])
|
131
133
|
@store_values << @options[:store]
|
132
134
|
@store_values << @options[:sortable]
|
133
|
-
@store_values << @options[:collapsible]
|
135
|
+
@store_values << @options[:collapsible]
|
134
136
|
@store_values = @store_values.flatten.uniq.compact
|
135
137
|
@spelling = @options[:spelling]
|
136
138
|
end
|
@@ -195,10 +197,10 @@ module XapianFu #:nodoc:
|
|
195
197
|
#
|
196
198
|
# The <tt>:page</tt> option sets which page of results to return.
|
197
199
|
# Defaults to 1.
|
198
|
-
#
|
200
|
+
#
|
199
201
|
# The <tt>:order</tt> option specifies the stored field to order
|
200
202
|
# the results by (instead of the default search result weight).
|
201
|
-
#
|
203
|
+
#
|
202
204
|
# The <tt>:reverse</tt> option reverses the order of the results,
|
203
205
|
# so lowest search weight first (or lowest stored field value
|
204
206
|
# first).
|
@@ -213,9 +215,13 @@ module XapianFu #:nodoc:
|
|
213
215
|
# enabled, spelling suggestions are available using the
|
214
216
|
# XapianFu::ResultSet <tt>corrected_query</tt> method.
|
215
217
|
#
|
218
|
+
# The first parameter can also be <tt>:all</tt> or
|
219
|
+
# <tt>:nothing</tt>, to match all documents or no documents
|
220
|
+
# respectively.
|
221
|
+
#
|
216
222
|
# For additional options on how the query is parsed, see
|
217
223
|
# XapianFu::QueryParser
|
218
|
-
|
224
|
+
|
219
225
|
def search(q, options = {})
|
220
226
|
defaults = { :page => 1, :reverse => false,
|
221
227
|
:boolean => true, :boolean_anycase => true, :wildcards => true,
|
@@ -227,15 +233,29 @@ module XapianFu #:nodoc:
|
|
227
233
|
per_page = per_page.to_i rescue 10
|
228
234
|
offset = page * per_page
|
229
235
|
qp = XapianFu::QueryParser.new({ :database => self }.merge(options))
|
230
|
-
query = qp.parse_query(q.to_s)
|
236
|
+
query = qp.parse_query(q.is_a?(Symbol) ? q : q.to_s)
|
237
|
+
query = filter_query(query, options[:filter]) if options[:filter]
|
231
238
|
enquiry = Xapian::Enquire.new(ro)
|
232
239
|
setup_ordering(enquiry, options[:order], options[:reverse])
|
233
240
|
if options[:collapse]
|
234
241
|
enquiry.collapse_key = XapianDocValueAccessor.value_key(options[:collapse])
|
235
242
|
end
|
243
|
+
if options[:facets]
|
244
|
+
spies = options[:facets].inject({}) do |accum, name|
|
245
|
+
accum[name] = spy = Xapian::ValueCountMatchSpy.new(XapianDocValueAccessor.value_key(name))
|
246
|
+
enquiry.add_matchspy(spy)
|
247
|
+
accum
|
248
|
+
end
|
249
|
+
end
|
236
250
|
enquiry.query = query
|
237
|
-
|
238
|
-
|
251
|
+
|
252
|
+
ResultSet.new(:mset => enquiry.mset(offset, per_page),
|
253
|
+
:current_page => page + 1,
|
254
|
+
:per_page => per_page,
|
255
|
+
:corrected_query => qp.corrected_query,
|
256
|
+
:spies => spies,
|
257
|
+
:xapian_db => self
|
258
|
+
)
|
239
259
|
end
|
240
260
|
|
241
261
|
# Run the given block in a XapianDB transaction. Any changes to the
|
@@ -272,6 +292,22 @@ module XapianFu #:nodoc:
|
|
272
292
|
ro.reopen
|
273
293
|
end
|
274
294
|
|
295
|
+
def serialize_value(field, value, type = nil)
|
296
|
+
if sortable_fields.include?(field)
|
297
|
+
Xapian.sortable_serialise(value)
|
298
|
+
else
|
299
|
+
(type || fields[field] || Object).to_xapian_fu_storage_value(value)
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
def unserialize_value(field, value, type = nil)
|
304
|
+
if sortable_fields.include?(field)
|
305
|
+
Xapian.sortable_unserialise(value)
|
306
|
+
else
|
307
|
+
(type || fields[field] || Object).from_xapian_fu_storage_value(value)
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
275
311
|
private
|
276
312
|
|
277
313
|
# Setup the writable database
|
@@ -317,12 +353,17 @@ module XapianFu #:nodoc:
|
|
317
353
|
@unindexed_fields = []
|
318
354
|
@store_values = []
|
319
355
|
@sortable_fields = {}
|
356
|
+
@boolean_fields = []
|
320
357
|
return nil if field_options.nil?
|
321
358
|
default_opts = {
|
322
359
|
:store => true,
|
323
360
|
:index => true,
|
324
361
|
:type => String
|
325
362
|
}
|
363
|
+
boolean_default_opts = default_opts.merge(
|
364
|
+
:store => false,
|
365
|
+
:index => false
|
366
|
+
)
|
326
367
|
# Convert array argument to hash, with String as default type
|
327
368
|
if field_options.is_a? Array
|
328
369
|
fohash = { }
|
@@ -332,16 +373,62 @@ module XapianFu #:nodoc:
|
|
332
373
|
field_options.each do |name,opts|
|
333
374
|
# Handle simple setup by type only
|
334
375
|
opts = { :type => opts } unless opts.is_a? Hash
|
335
|
-
|
376
|
+
if opts[:boolean]
|
377
|
+
opts = boolean_default_opts.merge(opts)
|
378
|
+
else
|
379
|
+
opts = default_opts.merge(opts)
|
380
|
+
end
|
336
381
|
@store_values << name if opts[:store]
|
337
382
|
@sortable_fields[name] = {:range_prefix => opts[:range_prefix], :range_postfix => opts[:range_postfix]} if opts[:sortable]
|
338
383
|
@unindexed_fields << name if opts[:index] == false
|
384
|
+
@boolean_fields << name if opts[:boolean]
|
339
385
|
@fields[name] = opts[:type]
|
340
386
|
end
|
341
387
|
@fields
|
342
388
|
end
|
343
|
-
|
389
|
+
|
390
|
+
def filter_query(query, filter)
|
391
|
+
subqueries = filter.map do |field, values|
|
392
|
+
values = Array(values)
|
393
|
+
|
394
|
+
if sortable_fields[field]
|
395
|
+
sortable_filter_query(field, values)
|
396
|
+
elsif boolean_fields.include?(field)
|
397
|
+
boolean_filter_query(field, values)
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
401
|
+
combined_subqueries = Xapian::Query.new(Xapian::Query::OP_AND, subqueries)
|
402
|
+
|
403
|
+
Xapian::Query.new(Xapian::Query::OP_FILTER, query, combined_subqueries)
|
404
|
+
end
|
405
|
+
|
406
|
+
def sortable_filter_query(field, values)
|
407
|
+
subqueries = values.map do |value|
|
408
|
+
from, to = value.split("..")
|
409
|
+
slot = XapianDocValueAccessor.value_key(field)
|
410
|
+
|
411
|
+
if from.empty?
|
412
|
+
Xapian::Query.new(Xapian::Query::OP_VALUE_LE, slot, Xapian.sortable_serialise(to.to_f))
|
413
|
+
elsif to.nil?
|
414
|
+
Xapian::Query.new(Xapian::Query::OP_VALUE_GE, slot, Xapian.sortable_serialise(from.to_f))
|
415
|
+
else
|
416
|
+
Xapian::Query.new(Xapian::Query::OP_VALUE_RANGE, slot, Xapian.sortable_serialise(from.to_f), Xapian.sortable_serialise(to.to_f))
|
417
|
+
end
|
418
|
+
end
|
419
|
+
|
420
|
+
Xapian::Query.new(Xapian::Query::OP_OR, subqueries)
|
421
|
+
end
|
422
|
+
|
423
|
+
def boolean_filter_query(field, values)
|
424
|
+
subqueries = values.map do |value|
|
425
|
+
Xapian::Query.new("X#{field.to_s.upcase}#{value.to_s.downcase}")
|
426
|
+
end
|
427
|
+
|
428
|
+
Xapian::Query.new(Xapian::Query::OP_OR, subqueries)
|
429
|
+
end
|
430
|
+
|
344
431
|
end
|
345
|
-
|
432
|
+
|
346
433
|
end
|
347
434
|
|
data/lib/xapian_fu/xapian_doc.rb
CHANGED
@@ -18,9 +18,15 @@ class DateTime #:nodoc:
|
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
21
|
+
class Array #:nodoc:
|
22
|
+
def to_xapian_fu_string
|
23
|
+
join(" ")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
21
27
|
module XapianFu #:nodoc:
|
22
28
|
require 'xapian_doc_value_accessor'
|
23
|
-
|
29
|
+
|
24
30
|
# Raised whenever a XapianDb is needed but has not been provided,
|
25
31
|
# such as when retrieving the terms list for a document
|
26
32
|
class XapianDbNotSet < XapianFuError ; end
|
@@ -33,26 +39,26 @@ module XapianFu #:nodoc:
|
|
33
39
|
# documents to the database. You usually don't need to instantiate
|
34
40
|
# them yourself unless you're doing something a bit advanced.
|
35
41
|
class XapianDoc
|
36
|
-
|
42
|
+
|
37
43
|
# A hash of the fields given to this object on initialize
|
38
44
|
attr_reader :fields
|
39
|
-
|
45
|
+
|
40
46
|
# An abitrary blob of data stored alongside the document in the
|
41
47
|
# Xapian database.
|
42
48
|
attr_reader :data
|
43
|
-
|
49
|
+
|
44
50
|
# The search score of this document when returned as part of a
|
45
51
|
# search result
|
46
52
|
attr_reader :weight
|
47
|
-
|
53
|
+
|
48
54
|
# The Xapian::Match object for this document when returned as part
|
49
55
|
# of a search result.
|
50
56
|
attr_reader :match
|
51
|
-
|
57
|
+
|
52
58
|
# The unsigned integer "primary key" for this document in the
|
53
59
|
# Xapian database.
|
54
60
|
attr_accessor :id
|
55
|
-
|
61
|
+
|
56
62
|
# The XapianDb object that this document was retrieved from, or
|
57
63
|
# should be stored in.
|
58
64
|
attr_accessor :db
|
@@ -66,7 +72,7 @@ module XapianFu #:nodoc:
|
|
66
72
|
# and term enumeration.
|
67
73
|
def initialize(doc, options = {})
|
68
74
|
@options = options
|
69
|
-
|
75
|
+
|
70
76
|
@fields = {}
|
71
77
|
if doc.is_a? Xapian::Match
|
72
78
|
match = doc
|
@@ -84,6 +90,9 @@ module XapianFu #:nodoc:
|
|
84
90
|
elsif doc.respond_to?(:has_key?) and doc.respond_to?("[]")
|
85
91
|
@fields = doc
|
86
92
|
@id = doc[:id] if doc.has_key?(:id)
|
93
|
+
# Handle initialisation from an object with a to_xapian_fu_string method
|
94
|
+
elsif doc.respond_to?(:to_xapian_fu_string)
|
95
|
+
@fields = { :content => doc.to_xapian_fu_string }
|
87
96
|
# Handle initialisation from anything else that can be coerced
|
88
97
|
# into a string
|
89
98
|
elsif doc.respond_to? :to_s
|
@@ -107,10 +116,10 @@ module XapianFu #:nodoc:
|
|
107
116
|
def values
|
108
117
|
@value_accessor ||= XapianDocValueAccessor.new(self)
|
109
118
|
end
|
110
|
-
|
119
|
+
|
111
120
|
# Return a list of terms that the db has for this document.
|
112
121
|
def terms
|
113
|
-
raise XapianFu::XapianDbNotSet unless db
|
122
|
+
raise XapianFu::XapianDbNotSet unless db
|
114
123
|
db.ro.termlist(id) if db.respond_to?(:ro) and db.ro and id
|
115
124
|
end
|
116
125
|
|
@@ -123,7 +132,7 @@ module XapianFu #:nodoc:
|
|
123
132
|
xapian_document.clear_values
|
124
133
|
add_values_to_xapian_document
|
125
134
|
# Clear and add terms
|
126
|
-
xapian_document.clear_terms
|
135
|
+
xapian_document.clear_terms
|
127
136
|
generate_terms
|
128
137
|
xapian_document
|
129
138
|
end
|
@@ -135,7 +144,7 @@ module XapianFu #:nodoc:
|
|
135
144
|
def xapian_document
|
136
145
|
@xapian_document ||= Xapian::Document.new
|
137
146
|
end
|
138
|
-
|
147
|
+
|
139
148
|
# Compare IDs with another XapianDoc
|
140
149
|
def ==(b)
|
141
150
|
if b.is_a?(XapianDoc)
|
@@ -144,7 +153,7 @@ module XapianFu #:nodoc:
|
|
144
153
|
super(b)
|
145
154
|
end
|
146
155
|
end
|
147
|
-
|
156
|
+
|
148
157
|
def inspect
|
149
158
|
s = ["<#{self.class.to_s} id=#{id}"]
|
150
159
|
s << "weight=%.5f" % weight if weight
|
@@ -167,7 +176,7 @@ module XapianFu #:nodoc:
|
|
167
176
|
def update
|
168
177
|
db.rw.replace_document(id, to_xapian_document)
|
169
178
|
end
|
170
|
-
|
179
|
+
|
171
180
|
# Set the stemmer to use for this document. Accepts any string
|
172
181
|
# that the Xapian::Stem class accepts (Either the English name for
|
173
182
|
# the language or the two letter ISO639 code). Can also be an
|
@@ -183,7 +192,7 @@ module XapianFu #:nodoc:
|
|
183
192
|
if @stemmer
|
184
193
|
@stemmer
|
185
194
|
else
|
186
|
-
@stemmer =
|
195
|
+
@stemmer =
|
187
196
|
if ! @options[:stemmer].nil?
|
188
197
|
@options[:stemmer]
|
189
198
|
elsif @options[:language]
|
@@ -196,7 +205,7 @@ module XapianFu #:nodoc:
|
|
196
205
|
@stemmer = StemFactory.stemmer_for(@stemmer)
|
197
206
|
end
|
198
207
|
end
|
199
|
-
|
208
|
+
|
200
209
|
# Return the stopper for this document. If not set on initialize
|
201
210
|
# by the :stopper or :language option, it will try the database's
|
202
211
|
# stopper and otherwise default to an English stopper..
|
@@ -218,7 +227,7 @@ module XapianFu #:nodoc:
|
|
218
227
|
end
|
219
228
|
end
|
220
229
|
|
221
|
-
# Return this document's language which is set on initialize, inherited
|
230
|
+
# Return this document's language which is set on initialize, inherited
|
222
231
|
# from the database or defaults to :english
|
223
232
|
def language
|
224
233
|
if @language
|
@@ -234,14 +243,14 @@ module XapianFu #:nodoc:
|
|
234
243
|
end
|
235
244
|
end
|
236
245
|
end
|
237
|
-
|
246
|
+
|
238
247
|
private
|
239
|
-
|
248
|
+
|
240
249
|
# Array of field names not to run through the TermGenerator
|
241
250
|
def unindexed_fields
|
242
251
|
db ? db.unindexed_fields : []
|
243
252
|
end
|
244
|
-
|
253
|
+
|
245
254
|
# Add all the fields to be stored as XapianDb values
|
246
255
|
def add_values_to_xapian_document
|
247
256
|
db.store_values.collect do |key|
|
@@ -271,12 +280,19 @@ module XapianFu #:nodoc:
|
|
271
280
|
# add value without field name
|
272
281
|
tg.send(index_method, v)
|
273
282
|
end
|
283
|
+
|
284
|
+
db.boolean_fields.each do |name|
|
285
|
+
Array(fields[name]).each do |value|
|
286
|
+
xapian_document.add_boolean_term("X#{name.to_s.upcase}#{value.to_s.downcase}")
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
274
290
|
xapian_document
|
275
291
|
end
|
276
|
-
|
292
|
+
|
277
293
|
end
|
278
|
-
|
279
|
-
|
294
|
+
|
295
|
+
|
280
296
|
class StemFactory
|
281
297
|
# Return a Xapian::Stem object for the given option. Accepts any
|
282
298
|
# string that the Xapian::Stem class accepts (Either the English
|
@@ -296,5 +312,5 @@ module XapianFu #:nodoc:
|
|
296
312
|
end
|
297
313
|
end
|
298
314
|
end
|
299
|
-
|
315
|
+
|
300
316
|
end
|
@@ -63,20 +63,31 @@ class Date #:nodoc:
|
|
63
63
|
end
|
64
64
|
end
|
65
65
|
|
66
|
+
class Object
|
67
|
+
def self.to_xapian_fu_storage_value(value)
|
68
|
+
value.to_s
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.from_xapian_fu_storage_value(value)
|
72
|
+
value
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
66
76
|
module XapianFu #:nodoc:
|
67
|
-
|
77
|
+
|
68
78
|
class ValueOutOfBounds < XapianFuError
|
69
79
|
end
|
70
|
-
|
80
|
+
|
71
81
|
# A XapianDocValueAccessor is used to provide the XapianDoc#values
|
72
82
|
# interface to read and write field values to a XapianDb. It is
|
73
83
|
# usually set up by a XapianDoc so you shouldn't need to set up your
|
74
84
|
# own.
|
75
85
|
class XapianDocValueAccessor
|
86
|
+
|
76
87
|
def initialize(xapian_doc)
|
77
88
|
@doc = xapian_doc
|
78
89
|
end
|
79
|
-
|
90
|
+
|
80
91
|
# Add the given <tt>value</tt> with the given <tt>key</tt> to the
|
81
92
|
# XapianDoc. If the value has a
|
82
93
|
# <tt>to_xapian_fu_storage_value</tt> method then it is used to
|
@@ -84,23 +95,12 @@ module XapianFu #:nodoc:
|
|
84
95
|
# is used. This is usually paired with a
|
85
96
|
# <tt>from_xapian_fu_storage_value</tt> class method on retrieval.
|
86
97
|
def store(key, value, type = nil)
|
87
|
-
|
88
|
-
|
89
|
-
if @doc.db && @doc.db.sortable_fields.include?(key)
|
90
|
-
converted_value = Xapian.sortable_serialise(value)
|
91
|
-
else
|
92
|
-
if type and type.respond_to?(:to_xapian_fu_storage_value)
|
93
|
-
converted_value = type.to_xapian_fu_storage_value(value)
|
94
|
-
else
|
95
|
-
converted_value = value.to_s
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
98
|
+
converted_value = @doc.db.serialize_value(key, value, type)
|
99
99
|
@doc.xapian_document.add_value(XapianDocValueAccessor.value_key(key), converted_value)
|
100
100
|
value
|
101
101
|
end
|
102
102
|
alias_method "[]=", :store
|
103
|
-
|
103
|
+
|
104
104
|
# Retrieve the value with the given <tt>key</tt> from the
|
105
105
|
# XapianDoc. <tt>key</tt> can be a symbol or string, in which case
|
106
106
|
# it's hashed to get an integer value number. Or you can give the
|
@@ -117,20 +117,15 @@ module XapianFu #:nodoc:
|
|
117
117
|
# empty string is returned.
|
118
118
|
def fetch(key, type = nil)
|
119
119
|
value = @doc.xapian_document.value(XapianDocValueAccessor.value_key(key))
|
120
|
-
|
121
|
-
if type and type.respond_to?(:from_xapian_fu_storage_value)
|
122
|
-
type.from_xapian_fu_storage_value(value)
|
123
|
-
else
|
124
|
-
value
|
125
|
-
end
|
120
|
+
@doc.db.unserialize_value(key, value, type)
|
126
121
|
end
|
127
122
|
alias_method "[]", :fetch
|
128
|
-
|
123
|
+
|
129
124
|
# Count the values stored in the XapianDoc
|
130
125
|
def size
|
131
126
|
@doc.xapian_document.values_count
|
132
127
|
end
|
133
|
-
|
128
|
+
|
134
129
|
# Remove the value with the given key from the XapianDoc and return it
|
135
130
|
def delete(key)
|
136
131
|
value = fetch(key)
|
@@ -142,6 +137,6 @@ module XapianFu #:nodoc:
|
|
142
137
|
# value number
|
143
138
|
def self.value_key(key)
|
144
139
|
(key.is_a?(Integer) ? key : Zlib.crc32(key.to_s))
|
145
|
-
end
|
140
|
+
end
|
146
141
|
end
|
147
142
|
end
|
data/spec/facets_spec.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
require File.expand_path('../lib/xapian_fu.rb', File.dirname(__FILE__))
|
2
|
+
|
3
|
+
tmp_dir = '/tmp/xapian_fu_test.db'
|
4
|
+
|
5
|
+
describe "Facets support" do
|
6
|
+
|
7
|
+
before do
|
8
|
+
@xdb = XapianFu::XapianDb.new(
|
9
|
+
:dir => tmp_dir, :create => true, :overwrite => true,
|
10
|
+
:fields => {
|
11
|
+
:name => { :index => true },
|
12
|
+
:age => { :type => Integer, :sortable => true },
|
13
|
+
:height => { :type => Float, :sortable => true }
|
14
|
+
}
|
15
|
+
)
|
16
|
+
|
17
|
+
@xdb << {:name => "John A", :age => 30, :height => 1.8}
|
18
|
+
@xdb << {:name => "John B", :age => 35, :height => 1.8}
|
19
|
+
@xdb << {:name => "John C", :age => 40, :height => 1.7}
|
20
|
+
@xdb << {:name => "John D", :age => 40, :height => 1.7}
|
21
|
+
@xdb << {:name => "Markus", :age => 35, :height => 1.7}
|
22
|
+
@xdb.flush
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should expose facets when searching" do
|
26
|
+
results = @xdb.search("john", {:facets => [:age, :height]})
|
27
|
+
|
28
|
+
results.facets[:age].should == [[30, 1], [35, 1], [40, 2]]
|
29
|
+
results.facets[:height].should == [[1.7, 2], [1.8, 2]]
|
30
|
+
|
31
|
+
results.facets.keys.map(&:to_s).sort == %w(age height)
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
data/spec/query_parser_spec.rb
CHANGED
@@ -37,6 +37,13 @@ describe QueryParser do
|
|
37
37
|
terms.should_not include "john"
|
38
38
|
end
|
39
39
|
|
40
|
+
it "should turn :all into a query with no terms" do
|
41
|
+
qp = QueryParser.new
|
42
|
+
qp.parse_query(:all).terms.should == []
|
43
|
+
qp.parse_query(:all).empty?.should be_false
|
44
|
+
qp.parse_query(:nothing).empty?.should be_true
|
45
|
+
end
|
46
|
+
|
40
47
|
end
|
41
48
|
|
42
49
|
end
|
data/spec/xapian_db_spec.rb
CHANGED
@@ -202,6 +202,46 @@ describe XapianDb do
|
|
202
202
|
xdb.size.should == 2
|
203
203
|
end
|
204
204
|
|
205
|
+
it "should generate boolean terms for multiple values" do
|
206
|
+
xdb = XapianDb.new(:dir => tmp_dir, :create => true,
|
207
|
+
:fields => {
|
208
|
+
:name => { :index => true },
|
209
|
+
:colors => { :boolean => true }
|
210
|
+
}
|
211
|
+
)
|
212
|
+
|
213
|
+
xdb << {:name => "Foo", :colors => [:red, :black]}
|
214
|
+
xdb << {:name => "Foo", :colors => [:red, :green]}
|
215
|
+
xdb << {:name => "Foo", :colors => [:blue, :yellow]}
|
216
|
+
|
217
|
+
xdb.flush
|
218
|
+
|
219
|
+
xdb.search("foo", :filter => {:colors => [:red]}).map(&:id).should == [1, 2]
|
220
|
+
xdb.search("foo", :filter => {:colors => [:black, :green]}).map(&:id).should == [1, 2]
|
221
|
+
|
222
|
+
xdb.search("red").should be_empty
|
223
|
+
end
|
224
|
+
|
225
|
+
it "should index boolean terms if asked to" do
|
226
|
+
xdb = XapianDb.new(:dir => tmp_dir, :create => true,
|
227
|
+
:fields => {
|
228
|
+
:name => { :index => true },
|
229
|
+
:colors => { :index => true, :boolean => true }
|
230
|
+
}
|
231
|
+
)
|
232
|
+
|
233
|
+
xdb << {:name => "Foo", :colors => [:red, :black]}
|
234
|
+
xdb << {:name => "Foo", :colors => [:red, :green]}
|
235
|
+
xdb << {:name => "Foo", :colors => [:blue, :yellow]}
|
236
|
+
|
237
|
+
xdb.flush
|
238
|
+
|
239
|
+
xdb.search("foo", :filter => {:colors => [:red]}).map(&:id).should == [1, 2]
|
240
|
+
xdb.search("foo", :filter => {:colors => [:black, :green]}).map(&:id).should == [1, 2]
|
241
|
+
|
242
|
+
xdb.search("red").map(&:id).should == [1, 2]
|
243
|
+
end
|
244
|
+
|
205
245
|
end
|
206
246
|
|
207
247
|
describe "search" do
|
@@ -394,6 +434,85 @@ describe XapianDb do
|
|
394
434
|
xdb.search("jon").should be_empty
|
395
435
|
xdb.search("jon", :synonyms => true).should_not be_empty
|
396
436
|
end
|
437
|
+
|
438
|
+
describe "with special queries" do
|
439
|
+
before do
|
440
|
+
@xdb = XapianDb.new
|
441
|
+
@xdb << "Doc 1"
|
442
|
+
@xdb << "Doc 2"
|
443
|
+
end
|
444
|
+
|
445
|
+
it "should return empty array on :nothing" do
|
446
|
+
@xdb.search(:nothing).should be_empty
|
447
|
+
end
|
448
|
+
|
449
|
+
it "should return all documents on :all" do
|
450
|
+
@xdb.search(:all).length.should eq 2
|
451
|
+
end
|
452
|
+
end
|
453
|
+
|
454
|
+
it "should allow to search by boolean terms" do
|
455
|
+
xdb = XapianDb.new(:dir => tmp_dir, :create => true,
|
456
|
+
:fields => {
|
457
|
+
:name => { :index => true },
|
458
|
+
:age => { :boolean => true },
|
459
|
+
:city => { :boolean => true }
|
460
|
+
}
|
461
|
+
)
|
462
|
+
|
463
|
+
xdb << {:name => "John A", :age => 10, :city => "London"}
|
464
|
+
xdb << {:name => "John B", :age => 11, :city => "Liverpool"}
|
465
|
+
xdb << {:name => "John C", :age => 12, :city => "Liverpool"}
|
466
|
+
|
467
|
+
xdb.flush
|
468
|
+
|
469
|
+
xdb.search("john").size.should == 3
|
470
|
+
xdb.search("john", :filter => {:age => 10}).map(&:id).should == [1]
|
471
|
+
xdb.search("john", :filter => {:age => [10, 12]}).map(&:id).should == [1, 3]
|
472
|
+
|
473
|
+
xdb.search("john", :filter => {:age => 10, :city => "Liverpool"}).map(&:id).should == []
|
474
|
+
xdb.search("john", :filter => {:city => "Liverpool"}).map(&:id).should == [2, 3]
|
475
|
+
xdb.search("john", :filter => {:age => 11..15, :city => "Liverpool"}).map(&:id).should == [2, 3]
|
476
|
+
|
477
|
+
xdb.search("liverpool").should be_empty
|
478
|
+
xdb.search("city:liverpool").map(&:id).should == [2, 3]
|
479
|
+
end
|
480
|
+
end
|
481
|
+
|
482
|
+
describe "filtering" do
|
483
|
+
before do
|
484
|
+
@xdb = XapianDb.new(
|
485
|
+
:dir => tmp_dir, :create => true, :overwrite => true,
|
486
|
+
:fields => {
|
487
|
+
:name => { :index => true },
|
488
|
+
:age => { :type => Integer, :sortable => true },
|
489
|
+
:height => { :type => Float, :sortable => true }
|
490
|
+
}
|
491
|
+
)
|
492
|
+
end
|
493
|
+
|
494
|
+
it "should filter results using value ranges" do
|
495
|
+
@xdb << {:name => "John", :age => 30, :height => 1.8}
|
496
|
+
@xdb << {:name => "John", :age => 35, :height => 1.9}
|
497
|
+
@xdb << {:name => "John", :age => 40, :height => 1.7}
|
498
|
+
@xdb << {:name => "Markus", :age => 35, :height => 1.7}
|
499
|
+
@xdb.flush
|
500
|
+
|
501
|
+
# Make sure we're combining queries using OP_FILTER by comparing
|
502
|
+
# the weights with and without filtering.
|
503
|
+
@xdb.search("markus")[0].weight.should == @xdb.search("markus", :filter => {:age => "35"})[0].weight
|
504
|
+
|
505
|
+
@xdb.search("john", :filter => {:age => "10..20"}).should be_empty
|
506
|
+
|
507
|
+
@xdb.search("john", :filter => {:age => "10..30"}).map(&:id).should == [1]
|
508
|
+
@xdb.search("john", :filter => {:age => "35.."}).map(&:id).should == [2, 3]
|
509
|
+
@xdb.search("john", :filter => {:age => "..35"}).map(&:id).should == [1, 2]
|
510
|
+
@xdb.search("john", :filter => {:age => ["..30", "40.."]}).map(&:id).should == [1, 3]
|
511
|
+
|
512
|
+
@xdb.search("john", :filter => {:age => "10..30", :height => "1.8"}).map(&:id).should == [1]
|
513
|
+
@xdb.search("john", :filter => {:age => "10..30", :height => "..1.8"}).map(&:id).should == [1]
|
514
|
+
@xdb.search("john", :filter => {:age => "10..30", :height => "1.9.."}).should be_empty
|
515
|
+
end
|
397
516
|
end
|
398
517
|
|
399
518
|
describe "add_doc" do
|
@@ -468,6 +587,21 @@ describe XapianDb do
|
|
468
587
|
docs.map { |d| d.id }.should == [1, 3]
|
469
588
|
end
|
470
589
|
|
590
|
+
it "should allow range queries without prefixes" do
|
591
|
+
xdb = XapianDb.new(:fields => {
|
592
|
+
:price => { :type => Integer, :sortable => true, :range_prefix => "$" },
|
593
|
+
:age => { :type => Integer, :sortable => true }
|
594
|
+
})
|
595
|
+
|
596
|
+
xdb << XapianDoc.new(:price => 10, :age => 40)
|
597
|
+
xdb << XapianDoc.new(:price => 20, :age => 35)
|
598
|
+
xdb << XapianDoc.new(:price => 45, :age => 30)
|
599
|
+
|
600
|
+
docs = xdb.search("$20..40 OR age:40..50")
|
601
|
+
|
602
|
+
docs.map { |d| d.id }.should == [1, 2]
|
603
|
+
end
|
604
|
+
|
471
605
|
it "should store values declared as to be collapsible" do
|
472
606
|
xdb = XapianDb.new(:collapsible => :group_id)
|
473
607
|
xdb << XapianDoc.new(:group_id => "666", :author => "Jim Jones")
|
data/spec/xapian_doc_spec.rb
CHANGED
@@ -37,6 +37,30 @@ describe XapianDoc do
|
|
37
37
|
xdb.ro.positionlist(doc.id, "once").first.should == nil
|
38
38
|
end
|
39
39
|
|
40
|
+
it "should tokenize an array given as a field" do
|
41
|
+
xdb = XapianDb.new
|
42
|
+
xdoc = xdb.documents.new(:colors => [:red, :green, :blue]).to_xapian_document
|
43
|
+
xdoc.terms.should be_a_kind_of Array
|
44
|
+
xdoc.terms.last.should be_a_kind_of Xapian::Term
|
45
|
+
terms = xdoc.terms.collect { |t| t.term }
|
46
|
+
terms.should include "red"
|
47
|
+
terms.should include "green"
|
48
|
+
terms.should include "blue"
|
49
|
+
terms.should_not include "redgreenblue"
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should tokenize an array given as the content" do
|
53
|
+
xdb = XapianDb.new
|
54
|
+
xdoc = xdb.documents.new([:red, :green, :blue]).to_xapian_document
|
55
|
+
xdoc.terms.should be_a_kind_of Array
|
56
|
+
xdoc.terms.last.should be_a_kind_of Xapian::Term
|
57
|
+
terms = xdoc.terms.collect { |t| t.term }
|
58
|
+
terms.should include "red"
|
59
|
+
terms.should include "green"
|
60
|
+
terms.should include "blue"
|
61
|
+
terms.should_not include "redgreenblue"
|
62
|
+
end
|
63
|
+
|
40
64
|
it "should tokenize a hash" do
|
41
65
|
xdb = XapianDb.new
|
42
66
|
xdoc = xdb.documents.new(:title => 'once upon a time').to_xapian_document
|
@@ -12,8 +12,14 @@ describe XapianDocValueAccessor do
|
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
15
|
+
before do
|
16
|
+
@xdb = XapianDb.new(:fields => {:name => {:index => true}, :city => {:store => true}})
|
17
|
+
@xdb << {:name => "John"}
|
18
|
+
@xdb.flush
|
19
|
+
end
|
20
|
+
|
15
21
|
it "should store and fetch values like a hash" do
|
16
|
-
values =
|
22
|
+
values = @xdb.documents.find(1).values
|
17
23
|
values.store(:city, "Leeds").should == "Leeds"
|
18
24
|
values.fetch(:city).should == "Leeds"
|
19
25
|
values[:city] = "London"
|
@@ -21,7 +27,7 @@ describe XapianDocValueAccessor do
|
|
21
27
|
end
|
22
28
|
|
23
29
|
it "should add and retrieve values from the Xapian::Document" do
|
24
|
-
doc =
|
30
|
+
doc = @xdb.documents.find(1)
|
25
31
|
values = XapianDocValueAccessor.new(doc)
|
26
32
|
lambda { values[:city] = "London" }.should change(doc.xapian_document, :values_count).by(1)
|
27
33
|
end
|
@@ -95,12 +101,13 @@ describe XapianDocValueAccessor do
|
|
95
101
|
end
|
96
102
|
|
97
103
|
it "should count the stored values when size is called" do
|
98
|
-
doc =
|
104
|
+
doc = @xdb.documents.find(1)
|
99
105
|
lambda { doc.values[:city] = "London" }.should change(doc.values, :size).by(1)
|
100
106
|
end
|
101
107
|
|
102
108
|
it "should delete values from the Xapian::Document" do
|
103
|
-
doc =
|
109
|
+
doc = @xdb.documents.find(1)
|
110
|
+
values = doc.values
|
104
111
|
doc.values[:city] = "Leeds"
|
105
112
|
lambda { doc.values.delete(:city) }.should change(doc.values, :size).by(-1)
|
106
113
|
doc.values[:city] = "London"
|
metadata
CHANGED
@@ -1,21 +1,22 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: xapian-fu
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 7
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 1
|
8
|
-
-
|
9
|
-
-
|
10
|
-
version: 1.
|
8
|
+
- 4
|
9
|
+
- 0
|
10
|
+
version: 1.4.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- John Leach
|
14
|
+
- Damian Janowski
|
14
15
|
autorequire:
|
15
16
|
bindir: bin
|
16
17
|
cert_chain: []
|
17
18
|
|
18
|
-
date:
|
19
|
+
date: 2012-03-13 00:00:00 Z
|
19
20
|
dependencies:
|
20
21
|
- !ruby/object:Gem::Dependency
|
21
22
|
name: rspec
|
@@ -107,6 +108,7 @@ files:
|
|
107
108
|
- spec/xapian_doc_spec.rb
|
108
109
|
- spec/xapian_db_spec.rb
|
109
110
|
- spec/stopper_factory_spec.rb
|
111
|
+
- spec/facets_spec.rb
|
110
112
|
- spec/fixtures/film_data/x86_64-linux~1.8.7/value.baseA
|
111
113
|
- spec/fixtures/film_data/x86_64-linux~1.8.7/position.baseA
|
112
114
|
- spec/fixtures/film_data/x86_64-linux~1.8.7/record.baseA
|
@@ -203,6 +205,7 @@ test_files:
|
|
203
205
|
- spec/xapian_doc_spec.rb
|
204
206
|
- spec/xapian_db_spec.rb
|
205
207
|
- spec/stopper_factory_spec.rb
|
208
|
+
- spec/facets_spec.rb
|
206
209
|
- spec/fixtures/film_data/x86_64-linux~1.8.7/value.baseA
|
207
210
|
- spec/fixtures/film_data/x86_64-linux~1.8.7/position.baseA
|
208
211
|
- spec/fixtures/film_data/x86_64-linux~1.8.7/record.baseA
|