busca 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b4bb8a3f394f361469f5737cff36bbf104fadfdc
4
+ data.tar.gz: 9427a9b9fbe01dae878c272de043b810e560f63d
5
+ SHA512:
6
+ metadata.gz: 19b15e5d14ba75446d2b5edf0880e16857b5a7b98a1df26e8839061e4b4bd7bc0f0b8835a710da534af88978832c1775c72ac8ad00a1e7b231a5ac56c4282d2d
7
+ data.tar.gz: b05df704a480f6e9a6e7be0549dea7bf264f67b5a6708620d6c01bb9a531a24dffa8af69e029bc6112b0362c49785b739e2549104f57c7dd6e55ab9f997b0feb
@@ -0,0 +1,3 @@
1
+ busca.sublime-project
2
+ busca.sublime-workspace
3
+ dump.rdb
@@ -0,0 +1,9 @@
1
+ # Busca Changelog
2
+
3
+ ## [0.0.2] - 2015-07-26
4
+
5
+ ### Changed
6
+ - Updated dependencies (Separa and Filtra).
7
+
8
+ ### Added
9
+ - Changelog
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2012 Julián Porta
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
@@ -0,0 +1,105 @@
1
+ Busca
2
+ ====
3
+
4
+ Busca is a simple redis search. Nothing more, nothing less.
5
+
6
+ Description
7
+ -----------
8
+
9
+ Busca is a simple redis search that uses bitmaps to index words and performs searches. It is NOT recommended for your *big data*, unless you have *big <sup>big</sup> memory*. Also, there are no tests (yet) of how it performs with a that *big data* (if you are feeling adventurous, let me know).
10
+
11
+ ## Installation
12
+
13
+ As usual, you can install it using rubygems.
14
+
15
+ ```
16
+ $ gem install busca
17
+ ```
18
+
19
+ ## What kind of sorcery is this?
20
+
21
+ Well, that's a fair question. Busca is a ruby gem that indexes text and perform searches. It does that using redis as a backend, which is nice, because redis is nice.
22
+
23
+ To be honest, I have no idea how a fulltext search works, but I'm pretty sure it's complex. Busca is simple and works (at least for me).
24
+
25
+ It started as a proof of concept, and *it's still* a proof of concept, but running in production in a couple of tiny sites.
26
+
27
+ ## Before usage
28
+
29
+ Busca works (more or less) like this:
30
+
31
+ * You create a new instance of Busca, something like `busca = Busca.new`
32
+ * You pass some *identifier* (more on this later) and a string to the `index` function. Something like `busca.index(101, "Hey, please index this fancy text for me, will you?")`
33
+ * Then, you search, like `result = busca.search("fancy")`
34
+
35
+ That's it. `result` now contains an array of the *identifiers* that contain the word "fancy".
36
+
37
+ This is pretty much it. Internally, however, a few things happen.
38
+
39
+ `Busca` uses two gems:
40
+
41
+ * [Separa](https://github.com/Porta/separa) splits the input (a string, an object, you name it) into an array of terms.
42
+ * [Filtra](https://github.com/Porta/filtra) filters that array (removing duplicates, stemming, changing case, removing stopwords) to only index the terms you really want to index and make a effective use of space.
43
+
44
+ While those gems are included in `Busca` and used by default, you're not tied to them. You can implement your own separator and filter based on your particular scenario. Feel free to check the docs of both in order to get an idea of what they do and how.
45
+
46
+ Overall, this is the flow:
47
+
48
+ #### Index:
49
+
50
+ * You create a new instance of `Busca`. This is where you pass any configuration.
51
+
52
+ * Then, you `index` something. A unique identifier is required first (in most cases, the id of whatever you're indexing, so you can retrieve it from it's store later). Then, whatever you want to index. It can be a string or a ruby object.
53
+
54
+ * `Busca` receives whatever you passed to index and passes that to the separator.
55
+
56
+ * Then, the resulting array is passed to the filter.
57
+
58
+ * The resulting array is then indexed (saved into redis).
59
+
60
+ ### Search
61
+
62
+ * With your instance of `Busca`, you call the `search` method with whatever you want to search for (string or object)
63
+
64
+ * Busca passes that through the same separator and filter used in the indexing process.
65
+
66
+ * The result is an array with the identifiers you passed on the indexing process.
67
+
68
+ * Done!
69
+
70
+ ## Usage
71
+
72
+ ### Index/search a string
73
+
74
+ ```ruby
75
+ document = <<-eos
76
+ Ay, marry, is't:
77
+ But to my mind, though I am native here
78
+ And to the manner born, it is a custom
79
+ More honour'd in the breach than the observance.
80
+ This heavy-headed revel east and west
81
+ Makes us traduced and tax'd of other nations:
82
+ They clepe us drunkards, and with swinish phrase
83
+ Soil our addition; and indeed it takes
84
+ From our achievements, though perform'd at height,
85
+ The pith and marrow of our attribute.
86
+ eos
87
+
88
+ busca = Busca.new
89
+ indexed_id = busca.index(123, document) #indexed_id return id assigned in the index.
90
+ #not especially relevant, but in case you need it for something
91
+ #... later on
92
+ result = busca.search("breach")
93
+ result == [123] #the identifier of the document.
94
+ ```
95
+
96
+
97
+
98
+
99
+
100
+
101
+
102
+
103
+
104
+
105
+
Binary file
@@ -0,0 +1,18 @@
1
+ # encoding: utf-8
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "busca"
5
+ s.version = "0.0.2"
6
+ s.summary = "Busca is a simple redis search"
7
+ s.description = "Busca is a simple redis search that uses bitmaps to index words"
8
+ s.authors = ["Julián Porta"]
9
+ s.email = ["julian@porta.sh"]
10
+ s.homepage = "https://github.com/Porta/busca"
11
+ s.files = `git ls-files`.split("\n")
12
+ s.license = "MIT"
13
+ s.add_development_dependency "cutest", '~>1.2'
14
+ s.add_runtime_dependency "redic", '~> 1.4'
15
+ s.add_runtime_dependency "msgpack", '~> 0.5'
16
+ s.add_runtime_dependency "filtra", '~> 0.0.2'
17
+ s.add_runtime_dependency "separa", '~> 0.0.3'
18
+ end
@@ -0,0 +1,78 @@
1
+ # Encoding: utf-8
2
+ require 'msgpack'
3
+ require 'redic'
4
+ require 'separa'
5
+ require 'filtra'
6
+
7
+ class Busca
8
+ NAMESPACE = 'Busca' #TODO: Confirm gem name
9
+ LUA_CACHE = Hash.new { |h, k| h[k] = Hash.new }
10
+ LUA_INDEX = File.expand_path("./lib/lua/index.lua")
11
+ LUA_SEARCH = File.expand_path("./lib/lua/search.lua")
12
+ LUA_REMOVE = File.expand_path("./lib/lua/remove.lua")
13
+
14
+ attr_reader :namespace, :redis, :separa, :filtra
15
+
16
+ def initialize(opts = {})
17
+ @namespace = opts[:namespace] || NAMESPACE
18
+ @redis = opts[:redis] || Redic.new
19
+ @separa = opts[:separa] || Separa.new
20
+ @filtra = opts[:filtra] || Filtra.new
21
+ end
22
+
23
+ def split_and_filter(words)
24
+ terms = @separa.call(words)
25
+ filtered = @filtra.call(terms)
26
+ return filtered
27
+ end
28
+
29
+ def index(document_id, string)
30
+ words = split_and_filter(string)
31
+
32
+ index_id = script( LUA_INDEX, 0,
33
+ @namespace.to_msgpack,
34
+ document_id.to_msgpack,
35
+ words.to_msgpack
36
+ )
37
+ return index_id
38
+ end
39
+
40
+
41
+ def search(string)
42
+ words = split_and_filter(string)
43
+ document_ids = script( LUA_SEARCH, 0,
44
+ @namespace.to_msgpack,
45
+ words.to_msgpack
46
+ )
47
+ bitstring = document_ids.unpack('B*').join("")
48
+ ids = []
49
+ bitstring.each_char.each_with_index{|c, i| c == "1" ? ids.push(i) : nil}
50
+ ids
51
+ # document_ids
52
+ end
53
+
54
+
55
+ def remove(id)
56
+ result = script( LUA_REMOVE, 0,
57
+ @namespace.to_msgpack,
58
+ id.to_msgpack
59
+ )
60
+ return result
61
+ end
62
+
63
+ private
64
+
65
+ def script(file, *args)
66
+ cache = LUA_CACHE[redis.url]
67
+
68
+ if cache.key?(file)
69
+ sha = cache[file]
70
+ else
71
+ src = File.read(file)
72
+ sha = redis.call("SCRIPT", "LOAD", src)
73
+ cache[file] = sha
74
+ end
75
+ redis.call("EVALSHA", sha, *args)
76
+ end
77
+
78
+ end
@@ -0,0 +1,36 @@
1
+ local namespace = cmsgpack.unpack(ARGV[1])
2
+ local document_id = cmsgpack.unpack(ARGV[2])
3
+ local words = cmsgpack.unpack(ARGV[3])
4
+
5
+ local function check_for_vacants()
6
+ local id = redis.call('RPOP', namespace .. ':vacants')
7
+ return id
8
+ end
9
+
10
+ local function save(document_id, words)
11
+ -- first try to get one available id from the vacants list
12
+ local id = check_for_vacants()
13
+
14
+ if id == false then
15
+ id = redis.call('INCR', namespace .. ':document:ids')
16
+ end
17
+
18
+ redis.call('SET', namespace .. ':document:' .. id, document_id)
19
+
20
+ return id
21
+ end
22
+
23
+
24
+ local function index(words, id)
25
+ for i, word in pairs(words) do
26
+ redis.call('SETBIT', namespace .. ':' .. word, id, 1)
27
+ redis.call('RPUSH', namespace .. ':document:' .. id .. ':words', word)
28
+ end
29
+ end
30
+
31
+
32
+ local id = save(document_id, words)
33
+
34
+ index(words, id)
35
+
36
+ return id
@@ -0,0 +1,34 @@
1
+ local namespace = cmsgpack.unpack(ARGV[1])
2
+ local id = cmsgpack.unpack(ARGV[2])
3
+
4
+
5
+ local function get_words(id)
6
+ local words = redis.call('LRANGE', namespace .. ':document:' .. id .. ':words', 0, -1)
7
+ return words
8
+ end
9
+
10
+ local function set_new_vacants(id)
11
+ local id = redis.call('RPUSH', namespace .. ':vacants', id)
12
+ return id
13
+ end
14
+
15
+ local function delete(id)
16
+ -- add document id to vacants list
17
+ set_new_vacants(id)
18
+ redis.call('DEL', namespace .. ':document:' .. id)
19
+
20
+ end
21
+
22
+ local function remove_from_index(words, id)
23
+ for i, word in pairs(words) do
24
+ redis.call('SETBIT', namespace .. ':' .. word, id, 0)
25
+ end
26
+ end
27
+
28
+
29
+ local words = get_words(id)
30
+ delete(id)
31
+
32
+ remove_from_index(words, id)
33
+
34
+ return true
@@ -0,0 +1,51 @@
1
+ local namespace = cmsgpack.unpack(ARGV[1])
2
+ local words = cmsgpack.unpack(ARGV[2])
3
+
4
+
5
+ local function join(list, char)
6
+ local temp_str = ""
7
+ for k, v in pairs( list ) do
8
+ temp_str = temp_str .. char .. v
9
+ end
10
+ temp_str = string.sub(temp_str, 2) --remove first '_'
11
+ return temp_str
12
+ end
13
+
14
+ local function namespace_keys(list)
15
+ local keys = {}
16
+ for k, v in pairs( list ) do
17
+ table.insert(keys, k, namespace .. ':' .. v)
18
+ end
19
+ return keys
20
+ end
21
+
22
+ local function temp_dest_key(words)
23
+ local temp_key = join(words, '_')
24
+ local cached_key = namespace .. ':query_cache:' .. temp_key
25
+
26
+ local cached = redis.call('GET', cached_key)
27
+
28
+ return cached_key, cached
29
+ end
30
+
31
+ local function cache_results(temp_dest_key, results)
32
+ -- no need to cache anything since the bitop operation
33
+ -- already writes the result to cache
34
+ end
35
+
36
+
37
+ local function search(words)
38
+ local cached_key, cached = temp_dest_key(words)
39
+ if cached == false then
40
+ redis.call('BITOP', 'AND', cached_key, unpack( namespace_keys(words) ) )
41
+ cached = redis.call('GET', cached_key)
42
+ if cached == false then
43
+ cached = ""
44
+ end
45
+ end
46
+ return cached
47
+ end
48
+
49
+
50
+ local results = search(words)
51
+ return results
@@ -0,0 +1,2 @@
1
+ test:
2
+ cutest -r ./tests/helper.rb ./tests/*_test.rb
@@ -0,0 +1,45 @@
1
+
2
+ scope do
3
+ setup do
4
+ document = "this is some document that I've indexed"
5
+ busca = Busca.new()
6
+ busca.redis.call('flushdb')
7
+ [busca, document]
8
+ end
9
+
10
+ test "index a document and get it's id" do |busca, document|
11
+ document_id = busca.index(5, document)
12
+ assert_equal document_id, 1
13
+ end
14
+
15
+ test "index two documents" do |busca, document|
16
+ first = busca.index(3, document)
17
+ second = busca.index(7, "uno dos tres cuatro cinco seis")
18
+ assert_equal first, 1
19
+ assert_equal second, 2
20
+ end
21
+
22
+ test "trying a more complex separator" do |a, b|
23
+ separa = Separa.new(regexp: /,/)
24
+ busca = Busca.new(separa: separa)
25
+ document = "hey,there,this,is,sparta"
26
+ document_id = busca.index(1, document)
27
+ assert_equal 1, document_id
28
+ another_document = "i,cannot,believe,in,sparta"
29
+ busca.index(2, another_document)
30
+ sparta = busca.search("sparta")
31
+ assert_equal [1,2], sparta
32
+ end
33
+
34
+ test "trying a more complex document" do |a, b|
35
+ separa = Separa.new(Separa::Obj)
36
+ busca = Busca.new(separa: separa)
37
+ document = {uno: 'uno', dos: 'dos', tres: {cuatro: 4, cinco: 'SINCO'}}
38
+ document_id = busca.index(1, document)
39
+ assert_equal 1, document_id
40
+ cinco = busca.search(dos: 'dos')
41
+ assert_equal [1], cinco
42
+
43
+ end
44
+
45
+ end
@@ -0,0 +1,7 @@
1
+ require "cutest"
2
+ require_relative "../lib/busca"
3
+
4
+ prepare do
5
+ busca = Busca.new
6
+ busca.redis.call("FLUSHDB")
7
+ end
@@ -0,0 +1,34 @@
1
+
2
+ scope do
3
+ setup do
4
+ document = "this is some document that I've indexed"
5
+ busca = Busca.new()
6
+ busca.redis.call('flushdb')
7
+ [busca, document]
8
+ end
9
+
10
+ test "delete a document from index" do |busca, document|
11
+ first = busca.index(4, document)
12
+ second = busca.index(5, 'My name is julian porta and Im a good person')
13
+ busca.remove(second)
14
+ assert_equal busca.redis.call('GET', 'Busca:document:1'), "4"
15
+ assert_equal busca.redis.call('GET', 'Busca:document:2'), nil
16
+ end
17
+
18
+ test "should vacant an id upon document deletion" do |busca, document|
19
+ first = busca.index(11, document)
20
+ second = busca.index(12, 'My name is julian porta and Im a good person')
21
+ busca.remove(second)
22
+ assert_equal busca.redis.call('GET', 'Busca:document:2'), nil
23
+ assert_equal busca.redis.call('LINDEX', 'Busca:vacants', 0), "2"
24
+ end
25
+
26
+ test "should use the vacant id on new document index" do |busca, document|
27
+ first = busca.index(13, document)
28
+ assert_equal busca.redis.call('GET', 'Busca:document:1'), "13"
29
+ busca.redis.call('RPUSH', 'Busca:vacants', 3)
30
+ second = 'My name is julian porta and Im a good person'
31
+ busca.index(14, second)
32
+ assert_equal busca.redis.call('GET', 'Busca:document:3'), "14"
33
+ end
34
+ end
@@ -0,0 +1,74 @@
1
+ scope do
2
+
3
+ setup do
4
+ document = <<-eos
5
+ Ay, marry, is't:
6
+ But to my mind, though I am native here
7
+ And to the manner born, it is a custom
8
+ More honour'd in the breach than the observance.
9
+ This heavy-headed revel east and west
10
+ Makes us traduced and tax'd of other nations:
11
+ They clepe us drunkards, and with swinish phrase
12
+ Soil our addition; and indeed it takes
13
+ From our achievements, though perform'd at height,
14
+ The pith and marrow of our attribute.
15
+ So, oft it chances in particular men,
16
+ That for some vicious mole of nature in them,
17
+ As, in their birth--wherein they are not guilty,
18
+ Since nature cannot choose his origin--
19
+ By the o'ergrowth of some complexion,
20
+ Oft breaking down the pales and forts of reason,
21
+ Or by some habit that too much o'er-leavens
22
+ The form of plausive manners, that these men,
23
+ Carrying, I say, the stamp of one defect,
24
+ Being nature's livery, or fortune's star,--
25
+ Their virtues else--be they as pure as grace,
26
+ As infinite as man may undergo--
27
+ Shall in the general censure take corruption
28
+ From that particular fault: the dram of eale
29
+ Doth all the noble substance of a doubt
30
+ To his own scandal.
31
+ eos
32
+ compare = <<-eos
33
+ Angels and ministers of grace defend us!
34
+ Be thou a spirit of health or goblin damn'd,
35
+ Bring with thee airs from heaven or blasts from hell,
36
+ Be thy intents wicked or charitable,
37
+ Thou comest in such a questionable shape
38
+ That I will speak to thee: I'll call thee Hamlet,
39
+ King, father, royal Dane: O, answer me!
40
+ Let me not burst in ignorance; but tell
41
+ Why thy canonized bones, hearsed in death,
42
+ Have burst their cerements; why the sepulchre,
43
+ Wherein we saw thee quietly inurn'd,
44
+ Hath oped his ponderous and marble jaws,
45
+ To cast thee up again. What may this mean,
46
+ That thou, dead corse, again in complete steel
47
+ Revisit'st thus the glimpses of the moon,
48
+ Making night hideous; and we fools of nature
49
+ So horridly to shake our disposition
50
+ With thoughts beyond the reaches of our souls?
51
+ Say, why is this? wherefore? what should we do?
52
+ eos
53
+ control = <<-eos
54
+ This is a control string
55
+ eos
56
+ chongo = <<-eos
57
+ Grace is here
58
+ eos
59
+ busca = Busca.new()
60
+ busca.redis.call('flushdb')
61
+
62
+ [busca, document, compare, control, chongo]
63
+ end
64
+
65
+ test "search a document" do |busca, document, compare, control, chongo|
66
+ first = busca.index(1, document)
67
+ second = busca.index(2, compare)
68
+ third = busca.index(3, control)
69
+ fourth = busca.index(4, chongo)
70
+ result = busca.search('grace')
71
+ assert_equal result, [1, 2, 4]
72
+ end
73
+
74
+ end
metadata ADDED
@@ -0,0 +1,129 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: busca
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Julián Porta
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-06-23 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: cutest
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.2'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: redic
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.4'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.4'
41
+ - !ruby/object:Gem::Dependency
42
+ name: msgpack
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.5'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.5'
55
+ - !ruby/object:Gem::Dependency
56
+ name: filtra
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 0.0.2
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 0.0.2
69
+ - !ruby/object:Gem::Dependency
70
+ name: separa
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 0.0.3
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 0.0.3
83
+ description: Busca is a simple redis search that uses bitmaps to index words
84
+ email:
85
+ - julian@porta.sh
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - ".gitignore"
91
+ - CHANGELOG.md
92
+ - LICENSE
93
+ - README.md
94
+ - busca-0.0.1.gem
95
+ - busca.gemspec
96
+ - lib/busca.rb
97
+ - lib/lua/index.lua
98
+ - lib/lua/remove.lua
99
+ - lib/lua/search.lua
100
+ - makefile
101
+ - tests/busca_test.rb
102
+ - tests/helper.rb
103
+ - tests/remove_test.rb
104
+ - tests/search_test.rb
105
+ homepage: https://github.com/Porta/busca
106
+ licenses:
107
+ - MIT
108
+ metadata: {}
109
+ post_install_message:
110
+ rdoc_options: []
111
+ require_paths:
112
+ - lib
113
+ required_ruby_version: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ required_rubygems_version: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - ">="
121
+ - !ruby/object:Gem::Version
122
+ version: '0'
123
+ requirements: []
124
+ rubyforge_project:
125
+ rubygems_version: 2.4.6
126
+ signing_key:
127
+ specification_version: 4
128
+ summary: Busca is a simple redis search
129
+ test_files: []