eric-keyword_search 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.
@@ -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
+