bruce-keyword_search 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,112 @@
1
+ require File.dirname(__FILE__) << '/keyword_search/definition.rb'
2
+
3
+ module KeywordSearch
4
+
5
+ class ParseError < ::SyntaxError; end
6
+
7
+ class << self
8
+
9
+ %%{
10
+
11
+ machine parser;
12
+
13
+ action start {
14
+ tokstart = p;
15
+ }
16
+
17
+ action key {
18
+ key = word
19
+ results[key] ||= []
20
+ }
21
+
22
+ action default {
23
+ key = nil
24
+ }
25
+
26
+ action word {
27
+ word = data[tokstart..p-1]
28
+ }
29
+
30
+ action value {
31
+ (results[key || :default] ||= []) << [ word, positive_match ]
32
+ }
33
+
34
+ action negative_match {
35
+ positive_match = false
36
+ }
37
+
38
+ action positive_match {
39
+ positive_match = true
40
+ }
41
+
42
+ action quote { quotes += 1 }
43
+
44
+ action unquote { quotes -= 1 }
45
+
46
+ seperators = ' '+ | / *[,|] */ ;
47
+
48
+ bareword = ( [^ '"(:] . [^ "):]* ) > start % word ; # allow apostrophes
49
+ dquoted = '"' @ quote ( [^"]* > start % word ) :>> '"' @ unquote;
50
+ squoted = '\'' @ quote ( [^']* > start % word ) :>> '\'' @ unquote;
51
+
52
+ anyword = dquoted | squoted | bareword ;
53
+
54
+ anyvalue = anyword % value ;
55
+ multivalues = anyvalue ( seperators anyvalue )* ;
56
+ groupedvalues = '(' @ quote multivalues :>> ')' @ unquote;
57
+
58
+ value = groupedvalues | anyvalue ;
59
+
60
+ pair = bareword % key ':' value ;
61
+
62
+ value_only = value > default ;
63
+
64
+ match_mode = ('-' % negative_match | '+'? % positive_match ) ;
65
+
66
+ definition = match_mode? <: ( pair | value_only ) ;
67
+
68
+ definitions = definition ( ' '+ definition )*;
69
+
70
+ main := ' '* definitions? ' '* 0
71
+ @!{ raise ParseError, "At offset #{p}, near: '#{data[p,10]}'" };
72
+
73
+ }%%
74
+
75
+ def search(input_string, definition=nil, &block)
76
+ definition ||= Definition.new(&block)
77
+ results = parse(input_string)
78
+ results.each do |key, terms|
79
+ definition.handle(key, terms)
80
+ end
81
+ results
82
+ end
83
+
84
+ #######
85
+ private
86
+ #######
87
+
88
+ def parse(input) #:nodoc:
89
+ data = input + ' '
90
+ %% write data;
91
+ p = 0
92
+ eof = nil
93
+ word = nil
94
+ pe = data.length
95
+ key = nil
96
+ positive_match = nil
97
+ tokstart = nil
98
+ results = {}
99
+ quotes = 0
100
+ %% write init;
101
+ %% write exec;
102
+ unless quotes.zero?
103
+ raise ParseError, "Unclosed quotes"
104
+ end
105
+ results
106
+ end
107
+
108
+ end
109
+
110
+ end
111
+
112
+
@@ -0,0 +1,59 @@
1
+ module KeywordSearch
2
+
3
+ class Definition
4
+
5
+ class Keyword
6
+
7
+ attr_reader :name, :description, :handler
8
+ def initialize(name, description=nil, &handler)
9
+ @name, @description = name, description
10
+ @handler = handler
11
+ end
12
+
13
+ def handle(value, sign)
14
+ # If the handler is only expecting one argument,
15
+ # only give them the positive matches
16
+ if handler.arity == 1
17
+ handler.call(value) if sign
18
+ else
19
+ handler.call(value, sign)
20
+ end
21
+ end
22
+
23
+ end
24
+
25
+ def initialize
26
+ @default_keyword = nil
27
+ yield self if block_given?
28
+ end
29
+
30
+ def keywords
31
+ @keywords ||= []
32
+ end
33
+
34
+ def keyword(name, description=nil, &block)
35
+ keywords << Keyword.new(name, description, &block)
36
+ end
37
+
38
+ def default_keyword(name)
39
+ @default_keyword = name
40
+ end
41
+
42
+ def handle(key, values)
43
+ key = @default_keyword if key == :default
44
+ return false unless key
45
+ true_values, false_values = *values.partition { |v| v[1] }
46
+
47
+ # Get just the values
48
+ true_values.collect! { |v| v[0] }
49
+ false_values.collect! { |v| v[0] }
50
+
51
+ if k = keywords.detect { |kw| kw.name == key.to_sym}
52
+ k.handle(true_values, true)
53
+ k.handle(false_values, false) if false_values.length > 0
54
+ end
55
+ end
56
+
57
+ end
58
+
59
+ end
@@ -0,0 +1,319 @@
1
+ require 'test/unit'
2
+
3
+ require 'rubygems' rescue nil
4
+ require 'test/spec'
5
+
6
+ require File.dirname(__FILE__) + '/../lib/keyword_search'
7
+
8
+ context "KeywordSearch" do
9
+
10
+ NAME_AND_AGE = %<bruce williams age:26>
11
+ NAME_QUOTED_AND_AGE = %<"bruce williams" age:26>
12
+ NAME_AND_QUOTED_AGE = %<bruce williams age:"26">
13
+ DEFAULT_AGE_WITH_QUOTED_AGE = %<26 name:"bruce williams">
14
+ DEFAULT_AGE_WITH_SINGLE_QUOTED_AGE = %<26 name:'bruce williams'>
15
+ NAME_WITH_NESTED_SINGLE_QUOTES = %<"d'arcy d'uberville" age:28>
16
+ NAME_AND_GROUPED_AGE = %<coda hale age:(27)>
17
+ NAME_AND_GROUPED_QUOTED_AGE = %<coda hale age:("27")>
18
+ NAME_AND_GROUPED_QUOTED_AGES = %<coda hale age:("27" 34 '48')>
19
+ GROUPED_NAMES_AND_AGE = %<(coda bruce 'hale' "williams") age:20>
20
+
21
+ specify "default keyword" do
22
+ result = nil
23
+ KeywordSearch.search(NAME_AND_AGE) do |with|
24
+ with.default_keyword :name
25
+ with.keyword :name do |values|
26
+ result = values.join(' ')
27
+ end
28
+ end
29
+ assert_equal 'bruce williams', result
30
+ end
31
+
32
+ specify "grouped default keywords" do
33
+ result = nil
34
+ KeywordSearch.search(GROUPED_NAMES_AND_AGE) do |with|
35
+ with.default_keyword :name
36
+ with.keyword :name do |values|
37
+ result = values
38
+ end
39
+ end
40
+ assert_equal ['coda', 'bruce', 'hale', 'williams'], result
41
+ end
42
+
43
+ specify "unquoted keyword term" do
44
+ result = nil
45
+ KeywordSearch.search(NAME_AND_AGE) do |with|
46
+ with.keyword :age do |values|
47
+ result = Integer(values.first)
48
+ end
49
+ end
50
+ assert_equal 26, result
51
+ end
52
+
53
+ specify "unquoted grouped keyword term" do
54
+ result = nil
55
+ KeywordSearch.search(NAME_AND_GROUPED_AGE) do |with|
56
+ with.keyword :age do |values|
57
+ result = Integer(values.first)
58
+ end
59
+ end
60
+ assert_equal 27, result
61
+ end
62
+
63
+ specify "quoted grouped keyword term" do
64
+ result = nil
65
+ KeywordSearch.search(NAME_AND_GROUPED_QUOTED_AGE) do |with|
66
+ with.keyword :age do |values|
67
+ result = Integer(values.first)
68
+ end
69
+ end
70
+ assert_equal 27, result
71
+ end
72
+
73
+ specify "mixed grouped keyword terms" do
74
+ result = nil
75
+ KeywordSearch.search(NAME_AND_GROUPED_QUOTED_AGES) do |with|
76
+ with.keyword :age do |values|
77
+ result = values.map { |v| v.to_i }
78
+ end
79
+ end
80
+ assert_equal [27, 34, 48], result
81
+ end
82
+
83
+ specify "quoted default keyword term" do
84
+ result = nil
85
+ KeywordSearch.search(NAME_QUOTED_AND_AGE) do |with|
86
+ with.default_keyword :name
87
+ with.keyword :name do |values|
88
+ result = values.join(' ')
89
+ end
90
+ end
91
+ assert_equal 'bruce williams', result
92
+ end
93
+
94
+ specify "quoted keyword term" do
95
+ result = nil
96
+ KeywordSearch.search(NAME_AND_QUOTED_AGE) do |with|
97
+ with.keyword :age do |values|
98
+ result = Integer(values.first)
99
+ end
100
+ end
101
+ assert_equal 26, result
102
+ end
103
+
104
+ specify "quoted keyword term with whitespace" do
105
+ result = nil
106
+ KeywordSearch.search(DEFAULT_AGE_WITH_QUOTED_AGE) do |with|
107
+ with.default_keyword :age
108
+ with.keyword :name do |values|
109
+ result = values.first
110
+ end
111
+ end
112
+ assert_equal 'bruce williams', result
113
+ end
114
+
115
+ specify "single quoted keyword term with whitespace" do
116
+ result = nil
117
+ r = KeywordSearch.search(DEFAULT_AGE_WITH_SINGLE_QUOTED_AGE) do |with|
118
+ with.default_keyword :age
119
+ with.keyword :name do |values|
120
+ result = values.first
121
+ end
122
+ end
123
+ assert_equal 'bruce williams', result
124
+ end
125
+
126
+ specify "nested single quote is accumulated" do
127
+ result = nil
128
+ KeywordSearch.search(NAME_WITH_NESTED_SINGLE_QUOTES) do |with|
129
+ with.default_keyword :name
130
+ with.keyword :name do |values|
131
+ result = values.first
132
+ end
133
+ end
134
+ assert_equal %<d'arcy d'uberville>, result
135
+ end
136
+
137
+ specify "nested double quote is accumulated" do
138
+ result = nil
139
+ KeywordSearch.search(%<'he was called "jake"'>) do |with|
140
+ with.default_keyword :text
141
+ with.keyword :text do |values|
142
+ result = values.first
143
+ end
144
+ end
145
+ assert_equal %<he was called "jake">, result
146
+ end
147
+
148
+ specify "bare single quote in unquoted literal is accumulated" do
149
+ result = nil
150
+ KeywordSearch.search(%<bruce's age:27>) do |with|
151
+ with.default_keyword :text
152
+ with.keyword :text do |values|
153
+ result = values.first
154
+ end
155
+ end
156
+ assert_equal %<bruce's>, result
157
+ end
158
+
159
+ specify "single quoted literal is accumulated" do
160
+ result = nil
161
+ KeywordSearch.search(%<foo 'bruce williams' age:27>) do |with|
162
+ with.default_keyword :text
163
+ with.keyword :text do |values|
164
+ result = values.last
165
+ end
166
+ end
167
+ assert_equal %<bruce williams>, result
168
+ end
169
+
170
+ specify "period in literal is accumulated" do
171
+ result = nil
172
+ KeywordSearch.search(%<okay... age:27>) do |with|
173
+ with.default_keyword :text
174
+ with.keyword :text do |values|
175
+ result = values.first
176
+ end
177
+ end
178
+ assert_equal %<okay...>, result
179
+ end
180
+
181
+ specify "parse error results in exception" do
182
+ assert_raises(KeywordSearch::ParseError) do
183
+ KeywordSearch.search(%<we_do_not_allow:! or ::>) do |with|
184
+ with.default_keyword :text
185
+ with.keyword :text do |values|
186
+ result = values.first
187
+ end
188
+ end
189
+ end
190
+ end
191
+
192
+ specify "can use apostrophes in unquoted literal" do
193
+ result = nil
194
+ KeywordSearch.search(%<d'correct>) do |with|
195
+ with.default_keyword :text
196
+ with.keyword :text do |values|
197
+ result = values.first
198
+ end
199
+ end
200
+ assert_equal "d'correct", result
201
+ end
202
+
203
+ specify "can use apostrophes in unquoted literal values" do
204
+ result = nil
205
+ KeywordSearch.search(%<text:d'correct>) do |with|
206
+ with.default_keyword :text
207
+ with.keyword :text do |values|
208
+ result = values.first
209
+ end
210
+ end
211
+ assert_equal "d'correct", result
212
+ end
213
+
214
+ specify "cannot use an apostrophe at the beginning on an unquoted literal" do
215
+ assert_raises(KeywordSearch::ParseError) do
216
+ KeywordSearch.search(%<'thisiswrong>) do |with|
217
+ with.default_keyword :text
218
+ with.keyword :text do |values|
219
+ result = values.first
220
+ end
221
+ end
222
+ end
223
+ end
224
+
225
+ specify "keywords are case sensitive" do
226
+ result = nil
227
+ KeywordSearch.search(%<Text:justtesting>) do |with|
228
+ with.keyword :text do |values|
229
+ result = :small
230
+ end
231
+ with.keyword :Text do |values|
232
+ result = :big
233
+ end
234
+ end
235
+ assert_equal :big, result
236
+ end
237
+
238
+ specify "values are case sensitive" do
239
+ result = nil
240
+ KeywordSearch.search(%<text:Big>) do |with|
241
+ with.keyword :text do |values|
242
+ result = values.first
243
+ end
244
+ end
245
+ assert_equal 'Big', result
246
+ end
247
+
248
+ specify "spaces are condensed" do
249
+ result = nil
250
+ KeywordSearch.search(%< this is some text >) do |with|
251
+ with.default_keyword :text
252
+ with.keyword :text do |values|
253
+ result = values
254
+ end
255
+ end
256
+ assert_equal [], result.select { |v| v.match(/ /) }
257
+ end
258
+
259
+ specify "an empty search is successful" do
260
+ result = nil
261
+ KeywordSearch.search(%<>) do |with|
262
+ with.default_keyword :text
263
+ with.keyword :text do |values|
264
+ result = values
265
+ end
266
+ end
267
+ assert_nil result
268
+ end
269
+
270
+ specify 'a negative search' do
271
+ result = nil
272
+
273
+ KeywordSearch.search(%<-site:google.com>) do |with|
274
+ with.keyword :site do |values, positive|
275
+ result = [ values, positive ]
276
+ end
277
+ end
278
+ assert_equal [ [ 'google.com' ], false ], result
279
+ end
280
+
281
+ specify 'a positive search' do
282
+ result = nil
283
+
284
+ KeywordSearch.search(%<+site:google.com>) do |with|
285
+ with.keyword :site do |values, positive|
286
+ result = [ values, positive ]
287
+ end
288
+ end
289
+ assert_equal [ [ 'google.com' ], true ], result
290
+ end
291
+
292
+ specify 'a search with no sign' do
293
+ result = nil
294
+
295
+ KeywordSearch.search(%<site:google.com>) do |with|
296
+ with.keyword :site do |values, positive|
297
+ result = [ values, positive ]
298
+ end
299
+ end
300
+ assert_equal [ [ 'google.com' ], true ], result
301
+ end
302
+
303
+ specify 'a term should default to positive with no sign' do
304
+ result = nil
305
+
306
+ KeywordSearch.search(%<-site:google.com inurl:atom>) do |with|
307
+ with.keyword :inurl do |values, positive|
308
+ result = [ values, positive ]
309
+ end
310
+ end
311
+ assert_equal [ [ 'atom' ], true ], result
312
+ end
313
+ end
314
+
315
+
316
+
317
+
318
+
319
+