sphinx_for_dm 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/README ADDED
@@ -0,0 +1,10 @@
1
+ SphinxForDM
2
+ ===========
3
+
4
+ Introduction
5
+ ------------
6
+
7
+ Complete rip-off of acts_as_sphinx by Kent Sibilev.
8
+
9
+ Please submit patches to foysavas@gmail.com.
10
+
data/lib/sphinx.rb ADDED
@@ -0,0 +1,460 @@
1
+ require "socket"
2
+ # = sphinx.rb - Sphinx Client Library
3
+ #
4
+ # Author:: Dmytro Shteflyuk <mailto:kpumuk@kpumuk.info>.
5
+ # Copyright:: Copyright (c) 2006 Wildbit, LLC
6
+ # License:: Distributes under the same terms as Ruby
7
+ #
8
+ # This library is distributed under the terms of the Ruby license.
9
+ # You can freely distribute/modify this library.
10
+
11
+ # ==Sphinx Client Library
12
+ #
13
+ # The Sphinx Client Library is used to communicate with <tt>searchd</tt>
14
+ # daemon and get search results from Sphinx.
15
+ #
16
+ # ===Usage
17
+ #
18
+ # sphinx = Sphinx.new
19
+ # result = sphinx.query('test')
20
+ # ids = result[:matches].map { |id, value| id }.join(',')
21
+ # posts = Post.find :all, :conditions => "id IN (#{ids})"
22
+ #
23
+ # docs = posts.map { |post| post.body }
24
+ # excerpts = sphinx.build_excerpts(docs, 'index', 'test')
25
+ #
26
+ class Sphinx
27
+
28
+ # :stopdoc:
29
+ class SphinxError < StandardError; end
30
+ class SphinxConnectError < SphinxError; end
31
+ class SphinxResponseError < SphinxError; end
32
+ class SphinxInternalError < SphinxError; end
33
+ class SphinxTemporaryError < SphinxError; end
34
+ class SphinxUnknownError < SphinxError; end
35
+ # :startdoc:
36
+
37
+ # known searchd commands
38
+ SEARCHD_COMMAND_SEARCH = 0
39
+ SEARCHD_COMMAND_EXCERPT = 1
40
+
41
+ # current client-side command implementation versions
42
+ VER_COMMAND_SEARCH = 0x104
43
+ VER_COMMAND_EXCERPT = 0x100
44
+
45
+ # known searchd status codes
46
+ SEARCHD_OK = 0
47
+ SEARCHD_ERROR = 1
48
+ SEARCHD_RETRY = 2
49
+
50
+ # known match modes
51
+ SPH_MATCH_ALL = 0
52
+ SPH_MATCH_ANY = 1
53
+ SPH_MATCH_PHRASE = 2
54
+ SPH_MATCH_BOOLEAN = 3
55
+ SPH_MATCH_EXTENDED = 4
56
+
57
+ # known sort modes
58
+ SPH_SORT_RELEVANCE = 0
59
+ SPH_SORT_ATTR_DESC = 1
60
+ SPH_SORT_ATTR_ASC = 2
61
+ SPH_SORT_TIME_SEGMENTS = 3
62
+ SPH_SORT_EXTENDED = 4
63
+
64
+ # known attribute types
65
+ SPH_ATTR_INTEGER = 1
66
+ SPH_ATTR_TIMESTAMP = 2
67
+
68
+ # known grouping functions
69
+ SPH_GROUPBY_DAY = 0
70
+ SPH_GROUPBY_WEEK = 1
71
+ SPH_GROUPBY_MONTH = 2
72
+ SPH_GROUPBY_YEAR = 3
73
+ SPH_GROUPBY_ATTR = 4
74
+
75
+ # Constructs the Sphinx object and sets options to their default values.
76
+ def initialize
77
+ @host = 'localhost' # searchd host (default is "localhost")
78
+ @port = 3312 # searchd port (default is 3312)
79
+ @offset = 0 # how much records to seek from result-set start (default is 0)
80
+ @limit = 20 # how much records to return from result-set starting at offset (default is 20)
81
+ @mode = SPH_MATCH_ALL # query matching mode (default is SPH_MATCH_ALL)
82
+ @weights = [] # per-field weights (default is 1 for all fields)
83
+ @sort = SPH_SORT_RELEVANCE # match sorting mode (default is SPH_SORT_RELEVANCE)
84
+ @sortby = '' # attribute to sort by (defualt is "")
85
+ @min_id = 0 # min ID to match (default is 0)
86
+ @max_id = 0xFFFFFFFF # max ID to match (default is UINT_MAX)
87
+ @min = {} # attribute name to min-value hash (for range filters)
88
+ @max = {} # attribute name to max-value hash (for range filters)
89
+ @filter = {} # attribute name to values set hash (for values-set filters)
90
+ @groupby = '' # group-by attribute name
91
+ @groupfunc = SPH_GROUPBY_DAY # function to pre-process group-by attribute value with
92
+ @maxmatches = 1000 # max matches to retrieve
93
+
94
+ @error = '' # last error message
95
+ @warning = '' # last warning message
96
+ end
97
+
98
+ # Get last error message.
99
+ def last_error
100
+ @error
101
+ end
102
+
103
+ # Get last warning message.
104
+ def last_warning
105
+ @warning
106
+ end
107
+
108
+ # Set searchd server.
109
+ def set_server(host, port)
110
+ @host = host
111
+ @port = port
112
+ end
113
+
114
+ # Set match offset, count, and max number to retrieve.
115
+ def set_limits(offset, limit, max = 0)
116
+ @offset = offset
117
+ @limit = limit
118
+ @maxmatches = max if max > 0
119
+ end
120
+
121
+ # Set match mode.
122
+ def set_match_mode(mode)
123
+ @mode = mode
124
+ end
125
+
126
+ # Set sort mode.
127
+ def set_sort_mode(mode, sortby = '')
128
+ @sort = mode
129
+ @sortby = sortby
130
+ end
131
+
132
+ # Set per-field weights.
133
+ def set_weights(weights)
134
+ @weights = weights
135
+ end
136
+
137
+ # Set IDs range to match.
138
+ #
139
+ # Only match those records where document ID is beetwen <tt>min_id</tt> and <tt>max_id</tt>
140
+ # (including <tt>min_id</tt> and <tt>max_id</tt>).
141
+ def set_id_range(min_id, max_id)
142
+ @min_id = min_id
143
+ @max_id = max_id
144
+ end
145
+
146
+ # Set values filter.
147
+ #
148
+ # Only match those records where <tt>attr</tt> column values
149
+ # are in specified set.
150
+ def set_filter(attr, values)
151
+ @filter[attr] = values
152
+ end
153
+
154
+ # Set range filter.
155
+ #
156
+ # Only match those records where <tt>attr</tt> column value
157
+ # is beetwen <tt>min</tt> and <tt>max</tt> (including <tt>min</tt> and <tt>max</tt>).
158
+ def set_filter_range(attr, min, max)
159
+ @min[attr] = min
160
+ @max[attr] = max
161
+ end
162
+
163
+ # Set grouping.
164
+ #
165
+ # if grouping
166
+ def set_group_by(attr, func)
167
+ @groupby = attr
168
+ @groupfunc = func
169
+ end
170
+
171
+ # Connect to searchd server and run given search query.
172
+ #
173
+ # * <tt>query</tt> -- query string
174
+ # * <tt>index</tt> -- index name to query, default is "*" which means to query all indexes
175
+ #
176
+ # returns hash which has the following keys on success:
177
+ #
178
+ # * <tt>:matches</tt> -- hash which maps found document_id to ( "weight", "group" ) hash
179
+ # * <tt>:total</tt> -- total amount of matches retrieved (upto SPH_MAX_MATCHES, see sphinx.h)
180
+ # * <tt>:total_found</tt> -- total amount of matching documents in index
181
+ # * <tt>:time</tt> -- search time
182
+ # * <tt>:words</tt> -- hash which maps query terms (stemmed!) to ( :docs, :hits ) hash
183
+ def query(query, index = '*')
184
+ sock = connect
185
+
186
+ # build request
187
+
188
+ # mode and limits
189
+ req = [@offset, @limit, @mode, @sort].pack('NNNN')
190
+ req << [@sortby.length].pack('N')
191
+ req << @sortby
192
+ # query itself
193
+ req << [query.length].pack('N')
194
+ req << query
195
+ # weights
196
+ req << [@weights.length].pack('N')
197
+ req << @weights.pack('N' * @weights.length)
198
+ # indexes
199
+ req << [index.length].pack('N')
200
+ req << index
201
+ # id range
202
+ req << [@min_id.to_i, @max_id.to_i].pack('NN')
203
+
204
+ # filters
205
+ req << [@min.length + @filter.length].pack('N')
206
+ @min.each do |attribute, min|
207
+ req << [attribute.length].pack('N')
208
+ req << attribute
209
+ req << [0, min, @max[attribute]].pack('NNN')
210
+ end
211
+
212
+ @filter.each do |attribute, values|
213
+ req << [attribute.length].pack('N')
214
+ req << attribute
215
+ req << [values.length].pack('N')
216
+ req << values.pack('N' * values.length)
217
+ end
218
+
219
+ # group-by
220
+ req << [@groupfunc, @groupby.length].pack('NN')
221
+ req << @groupby
222
+
223
+ # max matches to retrieve
224
+ req << [@maxmatches].pack('N')
225
+
226
+ # send query, get response
227
+ len = req.length
228
+ # add header
229
+ req = [SEARCHD_COMMAND_SEARCH, VER_COMMAND_SEARCH, len].pack('nnN') + req
230
+ sock.send(req, 0)
231
+
232
+ response = get_response(sock, VER_COMMAND_SEARCH)
233
+
234
+ # parse response
235
+ result = {}
236
+ max = response.length # protection from broken response
237
+
238
+ #read schema
239
+ p = 0
240
+ fields = []
241
+ attrs = {}
242
+
243
+ nfields = response[p, 4].unpack('N*').first
244
+ p += 4
245
+ while nfields > 0 and p < max
246
+ nfields -= 1
247
+ len = response[p, 4].unpack('N*').first
248
+ p += 4
249
+ fields << response[p, len]
250
+ p += len
251
+ end
252
+ result[:fields] = fields
253
+
254
+ nattrs = response[p, 4].unpack('N*').first
255
+ p += 4
256
+ while nattrs > 0 && p < max
257
+ nattrs -= 1
258
+ len = response[p, 4].unpack('N*').first
259
+ p += 4
260
+ attr = response[p, len]
261
+ p += len
262
+ type = response[p, 4].unpack('N*').first
263
+ p += 4
264
+ attrs[attr.to_sym] = type;
265
+ end
266
+ result[:attrs] = attrs
267
+
268
+ # read match count
269
+ count = response[p, 4].unpack('N*').first
270
+ p += 4
271
+
272
+ # read matches
273
+ result[:matches] = {}
274
+ while count > 0 and p < max
275
+ count -= 1
276
+ doc, weight = response[p, 8].unpack('N*N*')
277
+ p += 8
278
+
279
+ result[:matches][doc] ||= {}
280
+ result[:matches][doc][:weight] = weight
281
+ attrs.each do |attr, type|
282
+ val = response[p, 4].unpack('N*').first
283
+ p += 4
284
+ result[:matches][doc][:attrs] ||= {}
285
+ result[:matches][doc][:attrs][attr] = val
286
+ end
287
+ end
288
+ result[:total], result[:total_found], result[:time], words = \
289
+ response[p, 16].unpack('N*N*N*N*')
290
+ result[:time] = '%.3f' % (result[:time] / 1000)
291
+ p += 16
292
+
293
+ result[:words] = {}
294
+ while words > 0 and p < max
295
+ words -= 1
296
+ len = response[p, 4].unpack('N*').first
297
+ p += 4
298
+ word = response[p, len]
299
+ p += len
300
+ docs, hits = response[p, 8].unpack('N*N*')
301
+ p += 8
302
+ result[:words][word] = {:docs => docs, :hits => hits}
303
+ end
304
+
305
+ result
306
+ end
307
+
308
+ # Connect to searchd server and generate exceprts from given documents.
309
+ #
310
+ # * <tt>index</tt> -- a string specifiying the index which settings will be used
311
+ # for stemming, lexing and case folding
312
+ # * <tt>docs</tt> -- an array of strings which represent the documents' contents
313
+ # * <tt>words</tt> -- a string which contains the words to highlight
314
+ # * <tt>opts</tt> is a hash which contains additional optional highlighting parameters.
315
+ #
316
+ # You can use following parameters:
317
+ # * <tt>:before_match</tt> -- a string to insert before a set of matching words, default is "<b>"
318
+ # * <tt>:after_match</tt> -- a string to insert after a set of matching words, default is "<b>"
319
+ # * <tt>:chunk_separator</tt> -- a string to insert between excerpts chunks, default is " ... "
320
+ # * <tt>:limit</tt> -- max excerpt size in symbols (codepoints), default is 256
321
+ # * <tt>:around</tt> -- how much words to highlight around each match, default is 5
322
+ #
323
+ # Returns an array of string excerpts on success.
324
+ def build_excerpts(docs, index, words, opts = {})
325
+ sock = connect
326
+
327
+ # fixup options
328
+ opts[:before_match] ||= '<b>';
329
+ opts[:after_match] ||= '</b>';
330
+ opts[:chunk_separator] ||= ' ... ';
331
+ opts[:limit] ||= 256;
332
+ opts[:around] ||= 5;
333
+
334
+ # build request
335
+
336
+ # v.1.0 req
337
+ req = [0, 1].pack('N2'); # mode=0, flags=1 (remove spaces)
338
+ # req index
339
+ req << [index.length].pack('N')
340
+ req << index
341
+ # req words
342
+ req << [words.length].pack('N')
343
+ req << words
344
+
345
+ # options
346
+ req << [opts[:before_match].length].pack('N')
347
+ req << opts[:before_match]
348
+ req << [opts[:after_match].length].pack('N')
349
+ req << opts[:after_match]
350
+ req << [opts[:chunk_separator].length].pack('N')
351
+ req << opts[:chunk_separator]
352
+ req << [opts[:limit].to_i, opts[:around].to_i].pack('NN')
353
+
354
+ # documents
355
+ req << [docs.size].pack('N');
356
+ docs.each do |doc|
357
+ req << [doc.length].pack('N')
358
+ req << doc
359
+ end
360
+
361
+ # send query, get response
362
+ len = req.length
363
+ # add header
364
+ req = [SEARCHD_COMMAND_EXCERPT, VER_COMMAND_EXCERPT, len].pack('nnN') + req
365
+ sock.send(req, 0)
366
+
367
+ response = get_response(sock, VER_COMMAND_EXCERPT)
368
+
369
+ # parse response
370
+ p = 0
371
+ res = []
372
+ rlen = response.length
373
+ docs.each do |doc|
374
+ len = response[p, 4].unpack('N*').first;
375
+ p += 4
376
+ if p + len > rlen
377
+ @error = 'incomplete reply'
378
+ raise SphinxResponseError, @error
379
+ end
380
+ res << response[p, len]
381
+ p += len
382
+ end
383
+ return res;
384
+ end
385
+
386
+ # Connect to searchd server.
387
+ def connect
388
+ begin
389
+ sock = TCPSocket.new(@host, @port)
390
+ rescue
391
+ @error = "connection to #{@host}:#{@port} failed"
392
+ raise SphinxConnectError, @error
393
+ end
394
+
395
+ v = sock.recv(4).unpack('N*').first
396
+ if v < 1
397
+ sock.close
398
+ @error = "expected searchd protocol version 1+, got version '#{v}'"
399
+ raise SphinxConnectError, @error
400
+ end
401
+
402
+ sock.send([1].pack('N'), 0)
403
+ sock
404
+ end
405
+ private :connect
406
+
407
+ # get and check response packet from searchd server
408
+ def get_response(sock, client_version)
409
+ header = sock.recv(8)
410
+ status, ver, len = header.unpack('n2N')
411
+ response = ''
412
+ left = len
413
+ while left > 0 do
414
+ begin
415
+ chunk = sock.recv(left)
416
+ if chunk
417
+ response << chunk
418
+ left -= chunk.length
419
+ end
420
+ rescue EOFError
421
+ end
422
+ end if left
423
+ sock.close
424
+
425
+ # check response
426
+ read = response.length
427
+ if not response or read != len
428
+ @error = len \
429
+ ? "failed to read searchd response (status=#{status}, ver=#{ver}, len=#{len}, read=#{read})" \
430
+ : "received zero-sized searchd response"
431
+ raise SphinxResponseError, @error
432
+ end
433
+
434
+ # check status
435
+ if status == SEARCHD_ERROR
436
+ @error = "searchd error: " + response[4,].to_s
437
+ raise SphinxInternalError, @error
438
+ end
439
+
440
+ if status == SEARCHD_RETRY
441
+ @error = "temporary searchd error: " + response[4,]
442
+ raise SphinxTemporaryError, @error
443
+ end
444
+
445
+ unless status == SEARCHD_OK
446
+ @error = "unknown status code '#{status}'"
447
+ raise SphinxUnknownError, @error
448
+ end
449
+
450
+ # check version
451
+ if ver < client_version
452
+ @warning = "searchd command v.%d.%d older than client's v.%d.%d, some options might not work" % \
453
+ ver >> 8, ver & 0xff, client_ver >> 8, client_ver & 0xff
454
+ end
455
+
456
+ return response
457
+ end
458
+ private :get_response
459
+
460
+ end
@@ -0,0 +1,88 @@
1
+ require "sphinx"
2
+
3
+ module SphinxForDataMapper
4
+ module ClassMethods
5
+ def acts_as_sphinx(options = {})
6
+ default_options = {:host => 'localhost', :port => 3312, :index => name.tableize}
7
+ write_inheritable_attribute 'sphinx_options', default_options.merge(options)
8
+ extend SphinxClassMethods
9
+ end
10
+ end
11
+
12
+ def self.included(receiver)
13
+ receiver.extend(ClassMethods)
14
+ end
15
+
16
+ module SphinxClassMethods
17
+ VALID_OPTIONS = %w[mode offset page limit index weights host
18
+ port range filter filter_range group_by sort_mode].map(&:to_sym)
19
+
20
+ def sphinx_index
21
+ read_inheritable_attribute('sphinx_options')[:index]
22
+ end
23
+
24
+ def sphinx_options
25
+ read_inheritable_attribute 'sphinx_options'
26
+ end
27
+
28
+ def ask_sphinx(query, options = {})
29
+ default_options = {:offset => 0, :limit => 20}
30
+ default_options.merge! sphinx_options
31
+ default_options.merge! options
32
+
33
+ if options[:page] && options[:limit]
34
+ options[:offset] = options[:limit] * (options[:page].to_i - 1)
35
+ options[:offset] = 0 if options[:offset] < 0
36
+ end
37
+
38
+ sphinx = Sphinx.new
39
+ sphinx.set_server options[:host], options[:port]
40
+ sphinx.set_limits options[:offset], options[:limit]
41
+ sphinx.set_weights options[:weights] if options[:weights]
42
+ sphinx.set_id_range options[:range] if options[:range]
43
+
44
+ options[:filter].each do |attr, values|
45
+ sphinx.set_filter attr, [*values]
46
+ end if options[:filter]
47
+
48
+ options[:filter_range].each do |attr, (min, max)|
49
+ sphinx.set_filter_range attr, min, max
50
+ end if options[:filter_range]
51
+
52
+ options[:group_by].each do |attr, func|
53
+ funcion = Sphinx.const_get("SPH_GROUPBY_#{func.to_s.upcase}") \
54
+ rescue raise("Unknown group by function #{func}")
55
+ sphinx.set_group_by attr, funcion
56
+ end if options[:group_by]
57
+
58
+ if options[:mode]
59
+ match_mode = Sphinx.const_get("SPH_MATCH_#{options[:mode].to_s.upcase}") \
60
+ rescue raise("Unknown search mode #{options[:mode]}")
61
+ sphinx.set_match_mode match_mode
62
+ end
63
+
64
+ if options[:sort_mode]
65
+ sort_mode, sort_expr = options[:sort_mode]
66
+ sort_mode = Sphinx.const_get("SPH_SORT_#{sort_mode.to_s.upcase}") \
67
+ rescue raise("Unknown sort mode #{sort_mode}")
68
+ sphinx.set_sort_mode sort_mode, sort_expr
69
+ end
70
+
71
+ sphinx.query query, options[:index]
72
+ end
73
+
74
+ def all_with_sphinx(query, options = {})
75
+ result = ask_sphinx(query, options.delete(:sphinx) || {})
76
+ records = result[:matches].empty? ? [] : all(({ :id.in => result[:matches].keys }).merge(options))
77
+ records = records.sort_by{|r| -result[:matches][r.id][:weight] }
78
+ %w[total total_found time].map(&:to_sym).each do |method|
79
+ class << records; self end.send(:define_method, method) {result[method]}
80
+ end
81
+ records
82
+ end
83
+ end
84
+ end
85
+
86
+ class DataMapper::Base
87
+ include SphinxForDataMapper
88
+ end
data/scripts/sphinx.sh ADDED
@@ -0,0 +1,47 @@
1
+ #!/bin/sh
2
+ #
3
+ # start/stop searchd server.
4
+
5
+ if ! [ -x /usr/local/bin/searchd ]; then
6
+ exit 0
7
+ fi
8
+
9
+ case "$1" in
10
+ start)
11
+ echo -n "Starting sphinx searchd server:"
12
+ echo -n " searchd" ;
13
+ /sbin/start-stop-daemon --start --quiet --pidfile /var/run/searchd.pid --chdir /etc --exec /usr/local/bin/searchd
14
+ echo "."
15
+ ;;
16
+ stop)
17
+ echo -n "Stopping sphinx searchd server:"
18
+ echo -n " searchd" ;
19
+ /sbin/start-stop-daemon --stop --quiet --oknodo --pidfile /var/run/searchd.pid --exec /usr/local/bin/searchd
20
+ echo "."
21
+ ;;
22
+ reload)
23
+ echo -n "Reloading sphinx searchd server:"
24
+ echo -n " searchd"
25
+ /sbin/start-stop-daemon --stop --quiet --oknodo --pidfile /var/run/searchd.pid --signal 1
26
+ echo "."
27
+ ;;
28
+ force-reload)
29
+ $0 reload
30
+ ;;
31
+ reindex)
32
+ cd /etc
33
+ /usr/local/bin/indexer --rotate --quiet --all
34
+ ;;
35
+ restart)
36
+ echo -n "Restarting sphinx searchd server:"
37
+ echo -n " searchd"
38
+ /sbin/start-stop-daemon --stop --quiet --oknodo --pidfile /var/run/searchd.pid --exec /usr/local/bin/searchd
39
+ /sbin/start-stop-daemon --start --quiet --pidfile /var/run/searchd.pid --chdir /etc --exec /usr/local/bin/searchd
40
+ echo "."
41
+ ;;
42
+ *)
43
+ echo "Usage: /etc/init.d/searchd {start|stop|reload|restart|reindex}"
44
+ exit 1
45
+ ;;
46
+ esac
47
+ exit 0
@@ -0,0 +1,43 @@
1
+ namespace :dm do
2
+ namespace :sphinx do
3
+ desc "Run indexer"
4
+ task :index do
5
+ cd 'config' do
6
+ system 'indexer --all'
7
+ end
8
+ end
9
+
10
+ desc "Rotate idendexes and restart searchd server"
11
+ task :rotate do
12
+ cd 'config' do
13
+ system 'indexer --rotate --all'
14
+ end
15
+ end
16
+
17
+ desc "Start searchd server"
18
+ task :start do
19
+ if File.exists?('/var/run/searchd.pid')
20
+ puts 'Sphinx searchd server is already started.'
21
+ else
22
+ cd 'config' do
23
+ system 'searchd'
24
+ puts 'Sphinx searchd server started.'
25
+ end
26
+ end
27
+ end
28
+
29
+ desc "Stop searchd server"
30
+ task :stop do
31
+ unless File.exists?('/var/run/searchd.pid')
32
+ puts 'Sphinx searchd server is not running.'
33
+ else
34
+ pid = File.read('/var/run/searchd.pid').chomp
35
+ system "kill #{pid}"
36
+ puts 'Sphinx searchd server stopped.'
37
+ end
38
+ end
39
+
40
+ desc "Restart searchd server"
41
+ task :restart => [:stop, :start]
42
+ end
43
+ end
metadata ADDED
@@ -0,0 +1,50 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.9.4
3
+ specification_version: 1
4
+ name: sphinx_for_dm
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.0.1
7
+ date: 2008-02-24 00:00:00 -05:00
8
+ summary: Acts_as_sphinx for DataMapper
9
+ require_paths:
10
+ - lib
11
+ email: foysavas@gmail.com
12
+ homepage: http://www.foysavas.com
13
+ rubyforge_project:
14
+ description:
15
+ autorequire: sphinx_for_dm
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ post_install_message:
29
+ authors:
30
+ - Foy Savas
31
+ files:
32
+ - lib/sphinx_for_dm.rb
33
+ - lib/sphinx.rb
34
+ - scripts/sphinx.sh
35
+ - tasks/sphinx_for_dm_tasks.rake
36
+ - README
37
+ test_files: []
38
+
39
+ rdoc_options: []
40
+
41
+ extra_rdoc_files:
42
+ - README
43
+ executables: []
44
+
45
+ extensions: []
46
+
47
+ requirements: []
48
+
49
+ dependencies: []
50
+