awesome_search 1.0.1 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +0 -1
- data/Rakefile +11 -0
- data/VERSION +1 -1
- data/awesome_search.gemspec +16 -5
- data/lib/awesome/definitions/stopwords.rb +125 -0
- data/lib/awesome/search.rb +13 -3
- data/lib/awesome_search.rb +1 -0
- data/test/helper.rb +3 -2
- data/test/test_awesome_search.rb +13 -4
- data/test/test_multiple_filters.rb +1 -1
- metadata +24 -4
data/README.rdoc
CHANGED
@@ -2,7 +2,6 @@
|
|
2
2
|
|
3
3
|
* A helpful library to make searching more organized in the controller and views.
|
4
4
|
* Using this library will force you to write a class for each kind of search you want to use.
|
5
|
-
* It has features that allow easy configuration of searches tailored to work with rsolr.
|
6
5
|
|
7
6
|
== Usage
|
8
7
|
|
data/Rakefile
CHANGED
@@ -22,11 +22,22 @@ begin
|
|
22
22
|
"lib/awesome/definitions/bits.rb",
|
23
23
|
"lib/awesome/definitions/filters.rb",
|
24
24
|
"lib/awesome/definitions/locales.rb",
|
25
|
+
"lib/awesome/definitions/stopwords.rb",
|
25
26
|
"lib/awesome/definitions/types.rb",
|
26
27
|
"lib/awesome/search.rb",
|
27
28
|
"lib/awesome/super_search.rb",
|
28
29
|
"lib/awesome/triage.rb",
|
29
30
|
"lib/awesome_search.rb",
|
31
|
+
"test/search_classes/amazon.rb",
|
32
|
+
"test/search_classes/ebay.rb",
|
33
|
+
"test/search_classes/google.rb",
|
34
|
+
"test/search_classes/local.rb",
|
35
|
+
"test/helper.rb",
|
36
|
+
"test/test_awesome_search.rb",
|
37
|
+
"test/test_multiple.rb",
|
38
|
+
"test/test_multiple_filters.rb",
|
39
|
+
"test/test_multiple_locales.rb",
|
40
|
+
"test/test_multiple_types.rb",
|
30
41
|
"rails/init.rb",
|
31
42
|
"init.rb"
|
32
43
|
]
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.0
|
1
|
+
1.1.0
|
data/awesome_search.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{awesome_search}
|
8
|
-
s.version = "1.0
|
8
|
+
s.version = "1.1.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["pboling"]
|
12
|
-
s.date = %q{2010-
|
12
|
+
s.date = %q{2010-06-22}
|
13
13
|
s.description = %q{Organize complicated search results}
|
14
14
|
s.email = %q{peter.boling@peterboling.com}
|
15
15
|
s.extra_rdoc_files = [
|
@@ -26,17 +26,28 @@ Gem::Specification.new do |s|
|
|
26
26
|
"lib/awesome/definitions/bits.rb",
|
27
27
|
"lib/awesome/definitions/filters.rb",
|
28
28
|
"lib/awesome/definitions/locales.rb",
|
29
|
+
"lib/awesome/definitions/stopwords.rb",
|
29
30
|
"lib/awesome/definitions/types.rb",
|
30
31
|
"lib/awesome/search.rb",
|
31
32
|
"lib/awesome/super_search.rb",
|
32
33
|
"lib/awesome/triage.rb",
|
33
34
|
"lib/awesome_search.rb",
|
34
|
-
"rails/init.rb"
|
35
|
+
"rails/init.rb",
|
36
|
+
"test/helper.rb",
|
37
|
+
"test/search_classes/amazon.rb",
|
38
|
+
"test/search_classes/ebay.rb",
|
39
|
+
"test/search_classes/google.rb",
|
40
|
+
"test/search_classes/local.rb",
|
41
|
+
"test/test_awesome_search.rb",
|
42
|
+
"test/test_multiple.rb",
|
43
|
+
"test/test_multiple_filters.rb",
|
44
|
+
"test/test_multiple_locales.rb",
|
45
|
+
"test/test_multiple_types.rb"
|
35
46
|
]
|
36
47
|
s.homepage = %q{http://github.com/pboling/awesome_search}
|
37
48
|
s.rdoc_options = ["--charset=UTF-8"]
|
38
49
|
s.require_paths = ["lib"]
|
39
|
-
s.rubygems_version = %q{1.3.
|
50
|
+
s.rubygems_version = %q{1.3.7}
|
40
51
|
s.summary = %q{Organize complicated search results}
|
41
52
|
s.test_files = [
|
42
53
|
"test/helper.rb",
|
@@ -55,7 +66,7 @@ Gem::Specification.new do |s|
|
|
55
66
|
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
56
67
|
s.specification_version = 3
|
57
68
|
|
58
|
-
if Gem::Version.new(Gem::
|
69
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
59
70
|
s.add_development_dependency(%q<shoulda>, [">= 0"])
|
60
71
|
s.add_runtime_dependency(%q<activesupport>, [">= 2.1"])
|
61
72
|
else
|
@@ -0,0 +1,125 @@
|
|
1
|
+
module Awesome
|
2
|
+
module Definitions
|
3
|
+
module Stopwords
|
4
|
+
|
5
|
+
QUOTED_REGEX = /("[^"]*")/
|
6
|
+
UNQUOTED_REGEX = /"([^"]*)"/
|
7
|
+
RM_QUOTED_REGEX = /"[^"]*"/
|
8
|
+
BEG_OPERATORS = /^[+-]/
|
9
|
+
END_OPERATORS = /[,+-]$/
|
10
|
+
EXCLUSION_OPERATORS = /^[-]/
|
11
|
+
INCLUSION_OPERATORS = /^[+]/
|
12
|
+
|
13
|
+
def self.included(base)
|
14
|
+
base.extend ClassMethods
|
15
|
+
base.cattr_accessor :search_stopwords
|
16
|
+
base.cattr_accessor :verbose_stopwords
|
17
|
+
end
|
18
|
+
|
19
|
+
module ClassMethods
|
20
|
+
def stopwords(key = :none)
|
21
|
+
case key
|
22
|
+
when :none then
|
23
|
+
self.search_stopwords[:none]
|
24
|
+
when :standard then
|
25
|
+
self.search_stopwords[:standard]
|
26
|
+
when :custom then
|
27
|
+
self.search_stopwords[:custom]
|
28
|
+
when :both then
|
29
|
+
self.search_stopwords[:custom] | self.search_stopwords[:standard]
|
30
|
+
else
|
31
|
+
Rails.logger.warn("AwesomeSearch: Stopwords Key Invalid, defaulting to :both")
|
32
|
+
self.search_stopwords[:custom] | self.search_stopwords[:standard]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
#Instance Methods:
|
38
|
+
|
39
|
+
#remove the stopwords from regular search terms, BUT NOT from exact phrase searches (quoted)
|
40
|
+
#example:
|
41
|
+
# txt = "+hair \"in the\" on the grapes, \"middle fork\" wrath \"age of man\" -end"
|
42
|
+
def process_stopwords(txt = self.search_text)
|
43
|
+
#Needs to be set so highlighting will work properly (can't match quotes)
|
44
|
+
self.highlight_token_array(txt)
|
45
|
+
#Now put humpty dumpty back together without the nasty stopwords, sort the tokens by length
|
46
|
+
self.search_token_array(txt).join(" ")
|
47
|
+
end
|
48
|
+
|
49
|
+
def search_token_array(txt)
|
50
|
+
self.search_tokens ||= (self.quoted_exact_phrases_array(txt) | self.gowords_array(txt)).sort {|a,b| b.length <=> a.length }
|
51
|
+
end
|
52
|
+
|
53
|
+
def highlight_token_array(txt)
|
54
|
+
self.highlight_tokens ||= begin
|
55
|
+
array = (self.unquoted_exact_phrases_array(txt) | self.gowords_array(txt)).sort {|a,b| b.length <=> a.length }
|
56
|
+
remove_exclusions(array)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def set_clean_search_query
|
61
|
+
self.clean_search_query = self.highlight_tokens.join(" ")
|
62
|
+
end
|
63
|
+
|
64
|
+
def remove_exclusions(array)
|
65
|
+
array.map do |tok|
|
66
|
+
tok.match(Awesome::Definitions::Stopwords::EXCLUSION_OPERATORS) ?
|
67
|
+
nil :
|
68
|
+
tok.match(Awesome::Definitions::Stopwords::RM_QUOTED_REGEX) ?
|
69
|
+
tok :
|
70
|
+
tok.gsub(Awesome::Definitions::Stopwords::INCLUSION_OPERATORS, '')
|
71
|
+
end.compact
|
72
|
+
end
|
73
|
+
|
74
|
+
#All tokens that are quoted
|
75
|
+
def tokenize_quot(txt)
|
76
|
+
self.tokenize_quoted ||= txt.split(Awesome::Definitions::Stopwords::QUOTED_REGEX)
|
77
|
+
end
|
78
|
+
|
79
|
+
#All tokens that are quoted, in their unquoted form
|
80
|
+
def tokenize_unquot(txt)
|
81
|
+
self.tokenize_unquoted ||= txt.split(Awesome::Definitions::Stopwords::UNQUOTED_REGEX)
|
82
|
+
end
|
83
|
+
|
84
|
+
#Remove all tokens that are quoted
|
85
|
+
def tokenize_without_quot(txt)
|
86
|
+
self.tokenize_without_quoted ||= txt.split(Awesome::Definitions::Stopwords::RM_QUOTED_REGEX)
|
87
|
+
end
|
88
|
+
|
89
|
+
# ["\"in the\"", "\"middle fork\"", "\"age of man\""]
|
90
|
+
def quoted_exact_phrases_array(txt)
|
91
|
+
self.quoted_exact_phrases ||= self.tokenize_quot(txt) - self.tokenize_without_quot(txt) - ['']
|
92
|
+
end
|
93
|
+
|
94
|
+
# ["in the", "middle fork", "age of man"]
|
95
|
+
def unquoted_exact_phrases_array(txt)
|
96
|
+
self.unquoted_exact_phrases ||= self.tokenize_unquot(txt) - self.tokenize_without_quot(txt) - ['']
|
97
|
+
end
|
98
|
+
|
99
|
+
# "+hair on the grapes, wrath -end"
|
100
|
+
def query_wo_exact_phrases(txt)
|
101
|
+
self.query_without_exact_phrases ||= txt.gsub(Awesome::Definitions::Stopwords::QUOTED_REGEX, '')
|
102
|
+
end
|
103
|
+
|
104
|
+
# ["+hair", "on", "the", "grapes,", "wrath", "-end"]
|
105
|
+
def array_with_stopwords(txt)
|
106
|
+
qa = self.query_wo_exact_phrases(txt).split
|
107
|
+
qa.delete(',') #delete works on self (qa here), so won't work chained onto the statement above!
|
108
|
+
qa
|
109
|
+
end
|
110
|
+
|
111
|
+
# ["+hair", "grapes,", "wrath", "-end"]
|
112
|
+
def gowords_array(txt)
|
113
|
+
self.gowords ||= self.array_with_stopwords(txt).map do |token|
|
114
|
+
cleaned_token = self.clean_token(token)
|
115
|
+
self.stopwords.include?(cleaned_token) ? nil : cleaned_token.blank? ? nil : token
|
116
|
+
end.compact
|
117
|
+
end
|
118
|
+
|
119
|
+
def clean_token(token)
|
120
|
+
token.gsub(Awesome::Definitions::Stopwords::BEG_OPERATORS, '').gsub(Awesome::Definitions::Stopwords::END_OPERATORS, '')
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
data/lib/awesome/search.rb
CHANGED
@@ -15,6 +15,7 @@ module Awesome
|
|
15
15
|
|
16
16
|
#Some defaults if stopwords are set to be used (by default they are turned off)
|
17
17
|
@@search_stopwords ||= {:standard => %w(an and are as at be but by for if in into is it no not of on or s such t that the their then there these they this to was will with),
|
18
|
+
:none => [],
|
18
19
|
:custom => []}
|
19
20
|
|
20
21
|
def self.configure_search_types(&block)
|
@@ -37,6 +38,7 @@ module Awesome
|
|
37
38
|
|
38
39
|
attr_accessor(:search_text,
|
39
40
|
:search_query,
|
41
|
+
:clean_search_query,
|
40
42
|
:search_type,
|
41
43
|
:search_filters,
|
42
44
|
:search_locale,
|
@@ -61,19 +63,22 @@ module Awesome
|
|
61
63
|
:search_tokens,
|
62
64
|
:highlight_tokens)
|
63
65
|
|
66
|
+
alias :query :search_text
|
67
|
+
alias :processed_query :search_query
|
68
|
+
|
64
69
|
#CLASS METHODS
|
65
70
|
#Main focus of class methods is determining which sort of AwesomeSearch subclass we need to instantiate for the search
|
66
71
|
def initialize(*args)
|
67
72
|
@multiple_types_as_one = args.first[:multiple_types_as_one]
|
68
|
-
@stopwords = args.first[:stopwords] ?
|
73
|
+
@stopwords = !args.first[:stopwords].blank? ?
|
69
74
|
args.first[:stopwords].is_a?(Array) ?
|
70
75
|
args.first[:stopwords] :
|
71
76
|
args.first[:stopwords].is_a?(Symbol) ?
|
72
77
|
self.class.stopwords(args.first[:stopwords]) :
|
73
78
|
self.class.stopwords(:both) :
|
74
|
-
args.first[:stopwords]
|
79
|
+
args.first[:stopwords] == true ?
|
75
80
|
self.class.stopwords(:standard) :
|
76
|
-
|
81
|
+
self.class.stopwords(:none)
|
77
82
|
@page = args.first[:page]
|
78
83
|
@per_page = args.first[:per_page]
|
79
84
|
@search_text = args.first[:search_text] # a string
|
@@ -82,6 +87,7 @@ module Awesome
|
|
82
87
|
@search_tokens = args.first[:search_tokens] # an array of the query terms as tokens after being cleaned, unless passed in as param (not sure why this would ever be desired, but why not allow it jic?) When not set in args, will be set by clean_search_text methods
|
83
88
|
@highlight_tokens = args.first[:highlight_tokens] # an array of the query terms as unquoted tokens after being cleaned, unless passed in as param (not sure why this would ever be desired, but why not allow it jic?) When not set in args, will be set by clean_search_text methods
|
84
89
|
@search_query = self.clean_search_text # a string to be set based on the search text by removing the search modifiers from the search text
|
90
|
+
@clean_search_query = self.set_clean_search_query # Removed search tokens and modifiers (+) so Regexes can be run on this string
|
85
91
|
@search_type = args.first[:search_type] # a symring (symring methods are in the Bits mixin)
|
86
92
|
@search_locale = args.first[:search_locale] # a symring (symring methods are in the Bits mixin)
|
87
93
|
@found = nil
|
@@ -104,9 +110,13 @@ module Awesome
|
|
104
110
|
end
|
105
111
|
|
106
112
|
def clean_search_text
|
113
|
+
#puts "search_text: #{self.search_text}" if Awesome::Search.verbose
|
107
114
|
txt = Awesome::Triage.clean_search_text(self.search_text)
|
115
|
+
#puts "cleaning1: #{txt}" if Awesome::Search.verbose
|
108
116
|
txt = Awesome::Search.clean_search_text(txt, self.multiple_types_as_one)
|
117
|
+
#puts "cleaning2: #{txt}" if Awesome::Search.verbose
|
109
118
|
txt = self.process_stopwords(txt)
|
119
|
+
#puts "cleaning3: #{txt}" if Awesome::Search.verbose
|
110
120
|
txt
|
111
121
|
end
|
112
122
|
|
data/lib/awesome_search.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require "awesome/definitions/bits" unless defined?(Awesome::Definitions::Bits)
|
2
2
|
require "awesome/definitions/locales" unless defined?(Awesome::Definitions::Locales)
|
3
3
|
require "awesome/definitions/filters" unless defined?(Awesome::Definitions::Filters)
|
4
|
+
require "awesome/definitions/stopwords" unless defined?(Awesome::Definitions::Stopwords)
|
4
5
|
require "awesome/definitions/types" unless defined?(Awesome::Definitions::Types)
|
5
6
|
require "awesome/triage" unless defined?(Awesome::Triage)
|
6
7
|
require "awesome/search" unless defined?(Awesome::Search)
|
data/test/helper.rb
CHANGED
@@ -12,7 +12,8 @@ require "test/search_classes/google"
|
|
12
12
|
require "test/search_classes/local"
|
13
13
|
|
14
14
|
class Test::Unit::TestCase
|
15
|
-
|
15
|
+
#protect_types is a work in progress, doesn't work now.
|
16
|
+
Awesome::Search.protect_types = false
|
16
17
|
Awesome::Search.protect_filters = true
|
17
18
|
Awesome::Search.verbose = false
|
18
19
|
Awesome::Search.verbose_types = false
|
@@ -100,7 +101,7 @@ class Test::Unit::TestCase
|
|
100
101
|
[ ":all",
|
101
102
|
":every" ],
|
102
103
|
":spotted" =>
|
103
|
-
[ "spotted" ],
|
104
|
+
[ ":spotted" ],
|
104
105
|
":old" =>
|
105
106
|
[ ":old",
|
106
107
|
":ancient",
|
data/test/test_awesome_search.rb
CHANGED
@@ -7,8 +7,14 @@ class TestAwesomeSearch < Test::Unit::TestCase
|
|
7
7
|
@awesome = Awesome::Search.new({:search_text => "this is a test", :search_type => :isbn, :search_locale => :amazon})
|
8
8
|
end
|
9
9
|
|
10
|
+
should "return search_text" do
|
11
|
+
assert_equal 'this is a test', @awesome.search_text
|
12
|
+
end
|
10
13
|
should "return search_query" do
|
11
|
-
assert_equal 'this is a
|
14
|
+
assert_equal 'this test is a', @awesome.search_query
|
15
|
+
end
|
16
|
+
should "return clean_search_query" do
|
17
|
+
assert_equal 'this test is a', @awesome.clean_search_query
|
12
18
|
end
|
13
19
|
should "return search_type" do
|
14
20
|
assert_equal :isbn, @awesome.search_type
|
@@ -20,11 +26,14 @@ class TestAwesomeSearch < Test::Unit::TestCase
|
|
20
26
|
|
21
27
|
context "An Awesome::Search instance with query modifiers" do
|
22
28
|
setup do
|
23
|
-
@awesome = Awesome::Search.new({:search_text => ":ebay :sku this is a test"})
|
29
|
+
@awesome = Awesome::Search.new({:search_text => ":ebay :sku this is a +test"})
|
24
30
|
end
|
25
31
|
|
26
|
-
should "return search_query without query
|
27
|
-
assert_equal 'this is a
|
32
|
+
should "return search_query without query tokens" do
|
33
|
+
assert_equal '+test this is a', @awesome.search_query
|
34
|
+
end
|
35
|
+
should "return clean_search_query without query tokens and modifiers" do
|
36
|
+
assert_equal 'test this is a', @awesome.clean_search_query
|
28
37
|
end
|
29
38
|
should "search_type should be nil" do
|
30
39
|
assert_equal nil, @awesome.search_type
|
@@ -4,7 +4,7 @@ class TestMultipleFilters < Test::Unit::TestCase
|
|
4
4
|
|
5
5
|
context "#results_for with multiple filters" do
|
6
6
|
setup do
|
7
|
-
@searches = Awesome::Search.results_for(":local :amazon :text :isbn :old this is a test 1234567890", ":text", ":local", [":old",":spotted",":shiny",":new"])
|
7
|
+
@searches = Awesome::Search.results_for(":local :amazon :text :isbn :old :spotted :new this is a test 1234567890", ":text", ":local", [":old",":spotted",":shiny",":new"])
|
8
8
|
end
|
9
9
|
should "should return an array" do
|
10
10
|
assert @searches.is_a?(Array)
|
metadata
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: awesome_search
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
+
hash: 19
|
4
5
|
prerelease: false
|
5
6
|
segments:
|
6
7
|
- 1
|
7
|
-
- 0
|
8
8
|
- 1
|
9
|
-
|
9
|
+
- 0
|
10
|
+
version: 1.1.0
|
10
11
|
platform: ruby
|
11
12
|
authors:
|
12
13
|
- pboling
|
@@ -14,16 +15,18 @@ autorequire:
|
|
14
15
|
bindir: bin
|
15
16
|
cert_chain: []
|
16
17
|
|
17
|
-
date: 2010-
|
18
|
+
date: 2010-06-22 00:00:00 -04:00
|
18
19
|
default_executable:
|
19
20
|
dependencies:
|
20
21
|
- !ruby/object:Gem::Dependency
|
21
22
|
name: shoulda
|
22
23
|
prerelease: false
|
23
24
|
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
24
26
|
requirements:
|
25
27
|
- - ">="
|
26
28
|
- !ruby/object:Gem::Version
|
29
|
+
hash: 3
|
27
30
|
segments:
|
28
31
|
- 0
|
29
32
|
version: "0"
|
@@ -33,9 +36,11 @@ dependencies:
|
|
33
36
|
name: activesupport
|
34
37
|
prerelease: false
|
35
38
|
requirement: &id002 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
36
40
|
requirements:
|
37
41
|
- - ">="
|
38
42
|
- !ruby/object:Gem::Version
|
43
|
+
hash: 1
|
39
44
|
segments:
|
40
45
|
- 2
|
41
46
|
- 1
|
@@ -61,12 +66,23 @@ files:
|
|
61
66
|
- lib/awesome/definitions/bits.rb
|
62
67
|
- lib/awesome/definitions/filters.rb
|
63
68
|
- lib/awesome/definitions/locales.rb
|
69
|
+
- lib/awesome/definitions/stopwords.rb
|
64
70
|
- lib/awesome/definitions/types.rb
|
65
71
|
- lib/awesome/search.rb
|
66
72
|
- lib/awesome/super_search.rb
|
67
73
|
- lib/awesome/triage.rb
|
68
74
|
- lib/awesome_search.rb
|
69
75
|
- rails/init.rb
|
76
|
+
- test/helper.rb
|
77
|
+
- test/search_classes/amazon.rb
|
78
|
+
- test/search_classes/ebay.rb
|
79
|
+
- test/search_classes/google.rb
|
80
|
+
- test/search_classes/local.rb
|
81
|
+
- test/test_awesome_search.rb
|
82
|
+
- test/test_multiple.rb
|
83
|
+
- test/test_multiple_filters.rb
|
84
|
+
- test/test_multiple_locales.rb
|
85
|
+
- test/test_multiple_types.rb
|
70
86
|
has_rdoc: true
|
71
87
|
homepage: http://github.com/pboling/awesome_search
|
72
88
|
licenses: []
|
@@ -77,23 +93,27 @@ rdoc_options:
|
|
77
93
|
require_paths:
|
78
94
|
- lib
|
79
95
|
required_ruby_version: !ruby/object:Gem::Requirement
|
96
|
+
none: false
|
80
97
|
requirements:
|
81
98
|
- - ">="
|
82
99
|
- !ruby/object:Gem::Version
|
100
|
+
hash: 3
|
83
101
|
segments:
|
84
102
|
- 0
|
85
103
|
version: "0"
|
86
104
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
87
106
|
requirements:
|
88
107
|
- - ">="
|
89
108
|
- !ruby/object:Gem::Version
|
109
|
+
hash: 3
|
90
110
|
segments:
|
91
111
|
- 0
|
92
112
|
version: "0"
|
93
113
|
requirements: []
|
94
114
|
|
95
115
|
rubyforge_project:
|
96
|
-
rubygems_version: 1.3.
|
116
|
+
rubygems_version: 1.3.7
|
97
117
|
signing_key:
|
98
118
|
specification_version: 3
|
99
119
|
summary: Organize complicated search results
|