keyword_search 1.2.0 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +8 -0
- data/Manifest.txt +1 -4
- data/README.txt +0 -3
- data/Rakefile +4 -26
- data/lib/keyword_search.rb +256 -22
- data/lib/keyword_search.rl +85 -0
- data/lib/keyword_search/definition.rb +8 -7
- data/test/test_keyword_search.rb +51 -15
- metadata +13 -22
- data/lib/keyword_search/evaluator.rb +0 -35
- data/lib/keyword_search/grammar.rb +0 -21
- data/lib/keyword_search/parser.rb +0 -40
- data/lib/keyword_search/tokenizer.rb +0 -0
data/History.txt
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
= 1.3.0 / 2007-10-09
|
2
|
+
|
3
|
+
* Conversion to Ragel-based parser (faster, less resource-intensive)
|
4
|
+
* Better support for other character sets, allow apostrophes in
|
5
|
+
unquoted words
|
6
|
+
* Test suite now uses test/spec
|
7
|
+
* API Should be backwards compatible
|
8
|
+
|
1
9
|
= 1.2.0 / 2007-05-09
|
2
10
|
|
3
11
|
* Raises KeywordSearch::ParseError instead of returning an empty Hash if an error occurs during parsing
|
data/Manifest.txt
CHANGED
@@ -3,9 +3,6 @@ Manifest.txt
|
|
3
3
|
README.txt
|
4
4
|
Rakefile
|
5
5
|
lib/keyword_search.rb
|
6
|
+
lib/keyword_search.rl
|
6
7
|
lib/keyword_search/definition.rb
|
7
|
-
lib/keyword_search/evaluator.rb
|
8
|
-
lib/keyword_search/grammar.rb
|
9
|
-
lib/keyword_search/parser.rb
|
10
|
-
lib/keyword_search/tokenizer.rb
|
11
8
|
test/test_keyword_search.rb
|
data/README.txt
CHANGED
@@ -17,8 +17,6 @@ Various notes:
|
|
17
17
|
* Input is automatically downcased (both keywords and values should be assumed to be in lowercase)
|
18
18
|
|
19
19
|
Development Roadmap:
|
20
|
-
1.1:: Expand supported character set for keywords and values
|
21
|
-
(currently supports a-z)
|
22
20
|
2.0:: Add negation and grouping (will break API backwards compatibility)
|
23
21
|
|
24
22
|
== SYNOPSIS:
|
@@ -59,7 +57,6 @@ Here's an example of usage from Rails (though the library is generic, and could
|
|
59
57
|
|
60
58
|
== REQUIREMENTS:
|
61
59
|
|
62
|
-
* dhaka
|
63
60
|
* hoe
|
64
61
|
|
65
62
|
== INSTALL:
|
data/Rakefile
CHANGED
@@ -2,9 +2,8 @@
|
|
2
2
|
|
3
3
|
require 'rubygems'
|
4
4
|
require 'hoe'
|
5
|
-
require './lib/keyword_search.rb'
|
6
5
|
|
7
|
-
Hoe.new('keyword_search',
|
6
|
+
Hoe.new('keyword_search', '1.3.0') do |p|
|
8
7
|
p.rubyforge_name = 'codefluency'
|
9
8
|
p.summary = 'Generic support for extracting GMail-style search keywords/values from strings'
|
10
9
|
p.description = p.paragraphs_of('README.txt', 2..5).join("\n\n")
|
@@ -12,31 +11,10 @@ Hoe.new('keyword_search', KeywordSearch::VERSION) do |p|
|
|
12
11
|
p.author = 'Bruce Williams'
|
13
12
|
p.email = 'bruce@codefluency.com'
|
14
13
|
p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
|
15
|
-
p.extra_deps = [['dhaka', '= 2.1.0']]
|
16
14
|
end
|
17
15
|
|
18
|
-
|
19
|
-
|
20
|
-
parser = Dhaka::Parser.new(KeywordSearch::Grammar)
|
21
|
-
File.open('lib/keyword_search/parser.rb', 'w') do |file|
|
22
|
-
file << parser.compile_to_ruby_source_as('KeywordSearch::Parser')
|
23
|
-
end
|
16
|
+
rule '.rb' => '.rl' do |t|
|
17
|
+
sh "ragel -R #{t.source} | rlgen-ruby -o #{t.name}"
|
24
18
|
end
|
25
19
|
|
26
|
-
task :
|
27
|
-
require 'dhaka'
|
28
|
-
lexer = Dhaka::Lexer.new(KeywordSearch::LexerSpec)
|
29
|
-
File.open('lib/keyword_search/lexer.rb', 'w') do |file|
|
30
|
-
file << lexer.compile_to_ruby_source_as('KeywordSearch::Lexer')
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
task :rebuild_lexer do
|
35
|
-
require 'dhaka'
|
36
|
-
lexer = Dhaka::Lexer.new(KeywordSearch::LexerSpec)
|
37
|
-
File.open('lib/keyword_search/lexer.rb', 'w') do |file|
|
38
|
-
file << lexer.compile_to_ruby_source_as('KeywordSearch::Lexer')
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
# vim: syntax=Ruby
|
20
|
+
task :ragel => 'lib/keyword_search.rb'
|
data/lib/keyword_search.rb
CHANGED
@@ -1,31 +1,265 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
dirname = File.join(File.dirname(__FILE__), 'keyword_search')
|
4
|
-
%w|grammar parser lexer_spec lexer evaluator definition|.each do |dependency|
|
5
|
-
require File.join(dirname, dependency)
|
6
|
-
end
|
1
|
+
# line 1 "lib/keyword_search.rl"
|
2
|
+
require File.dirname(__FILE__) << '/keyword_search/definition.rb'
|
7
3
|
|
8
4
|
module KeywordSearch
|
9
|
-
|
10
|
-
class ParseError < ::SyntaxError; end
|
11
|
-
|
12
|
-
VERSION = '1.2.0'
|
13
|
-
|
5
|
+
|
6
|
+
class ParseError < ::SyntaxError; end
|
7
|
+
|
14
8
|
class << self
|
9
|
+
|
10
|
+
# line 44 "lib/keyword_search.rl"
|
11
|
+
|
15
12
|
def search(input_string, definition=nil, &block)
|
16
|
-
@evaluator ||= Evaluator.new
|
17
13
|
definition ||= Definition.new(&block)
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
results.each do |key, terms|
|
22
|
-
definition.handle(key, terms)
|
23
|
-
end
|
24
|
-
results
|
25
|
-
else
|
26
|
-
raise ParseError, "Unexpected token #{parse_result.unexpected_token.inspect}"
|
14
|
+
results = parse(input_string)
|
15
|
+
results.each do |key, terms|
|
16
|
+
definition.handle(key, terms)
|
27
17
|
end
|
18
|
+
results
|
19
|
+
end
|
20
|
+
|
21
|
+
#######
|
22
|
+
private
|
23
|
+
#######
|
24
|
+
|
25
|
+
def parse(input) #:nodoc:
|
26
|
+
data = input + ' '
|
27
|
+
|
28
|
+
# line 30 "lib/keyword_search.rb"
|
29
|
+
class << self
|
30
|
+
attr_accessor :_parser_actions
|
31
|
+
private :_parser_actions, :_parser_actions=
|
32
|
+
end
|
33
|
+
self._parser_actions = [
|
34
|
+
0, 1, 3, 1, 4, 2, 0, 2,
|
35
|
+
2, 1, 0
|
36
|
+
]
|
37
|
+
|
38
|
+
class << self
|
39
|
+
attr_accessor :_parser_key_offsets
|
40
|
+
private :_parser_key_offsets, :_parser_key_offsets=
|
41
|
+
end
|
42
|
+
self._parser_key_offsets = [
|
43
|
+
0, 0, 5, 8, 12, 15, 16, 17,
|
44
|
+
18
|
45
|
+
]
|
46
|
+
|
47
|
+
class << self
|
48
|
+
attr_accessor :_parser_trans_keys
|
49
|
+
private :_parser_trans_keys, :_parser_trans_keys=
|
50
|
+
end
|
51
|
+
self._parser_trans_keys = [
|
52
|
+
0, 32, 34, 39, 58, 32, 34, 58,
|
53
|
+
32, 34, 39, 58, 32, 34, 58, 34,
|
54
|
+
32, 39, 32, 34, 58, 0
|
55
|
+
]
|
56
|
+
|
57
|
+
class << self
|
58
|
+
attr_accessor :_parser_single_lengths
|
59
|
+
private :_parser_single_lengths, :_parser_single_lengths=
|
60
|
+
end
|
61
|
+
self._parser_single_lengths = [
|
62
|
+
0, 5, 3, 4, 3, 1, 1, 1,
|
63
|
+
3
|
64
|
+
]
|
65
|
+
|
66
|
+
class << self
|
67
|
+
attr_accessor :_parser_range_lengths
|
68
|
+
private :_parser_range_lengths, :_parser_range_lengths=
|
69
|
+
end
|
70
|
+
self._parser_range_lengths = [
|
71
|
+
0, 0, 0, 0, 0, 0, 0, 0,
|
72
|
+
0
|
73
|
+
]
|
74
|
+
|
75
|
+
class << self
|
76
|
+
attr_accessor :_parser_index_offsets
|
77
|
+
private :_parser_index_offsets, :_parser_index_offsets=
|
78
|
+
end
|
79
|
+
self._parser_index_offsets = [
|
80
|
+
0, 0, 6, 10, 15, 19, 21, 23,
|
81
|
+
25
|
82
|
+
]
|
83
|
+
|
84
|
+
class << self
|
85
|
+
attr_accessor :_parser_trans_targs_wi
|
86
|
+
private :_parser_trans_targs_wi, :_parser_trans_targs_wi=
|
87
|
+
end
|
88
|
+
self._parser_trans_targs_wi = [
|
89
|
+
8, 0, 5, 7, 0, 2, 1, 0,
|
90
|
+
3, 2, 0, 5, 7, 0, 4, 1,
|
91
|
+
0, 0, 4, 6, 5, 1, 0, 6,
|
92
|
+
7, 1, 0, 3, 2, 0
|
93
|
+
]
|
94
|
+
|
95
|
+
class << self
|
96
|
+
attr_accessor :_parser_trans_actions_wi
|
97
|
+
private :_parser_trans_actions_wi, :_parser_trans_actions_wi=
|
98
|
+
end
|
99
|
+
self._parser_trans_actions_wi = [
|
100
|
+
5, 3, 5, 5, 3, 5, 1, 0,
|
101
|
+
0, 0, 0, 8, 8, 0, 8, 1,
|
102
|
+
0, 0, 0, 0, 0, 1, 0, 0,
|
103
|
+
0, 1, 0, 0, 0, 0
|
104
|
+
]
|
105
|
+
|
106
|
+
class << self
|
107
|
+
attr_accessor :parser_start
|
108
|
+
end
|
109
|
+
self.parser_start = 1;
|
110
|
+
class << self
|
111
|
+
attr_accessor :parser_first_final
|
112
|
+
end
|
113
|
+
self.parser_first_final = 8;
|
114
|
+
class << self
|
115
|
+
attr_accessor :parser_error
|
116
|
+
end
|
117
|
+
self.parser_error = 0;
|
118
|
+
|
119
|
+
class << self
|
120
|
+
attr_accessor :parser_en_main
|
121
|
+
end
|
122
|
+
self.parser_en_main = 1;
|
123
|
+
|
124
|
+
# line 62 "lib/keyword_search.rl"
|
125
|
+
p = 0
|
126
|
+
pe = data.length
|
127
|
+
key = nil
|
128
|
+
tokstart = nil
|
129
|
+
results = {}
|
130
|
+
|
131
|
+
# line 133 "lib/keyword_search.rb"
|
132
|
+
begin
|
133
|
+
p ||= 0
|
134
|
+
pe ||= data.length
|
135
|
+
cs = parser_start
|
136
|
+
end
|
137
|
+
# line 68 "lib/keyword_search.rl"
|
138
|
+
|
139
|
+
# line 141 "lib/keyword_search.rb"
|
140
|
+
begin
|
141
|
+
_klen, _trans, _keys, _acts, _nacts = nil
|
142
|
+
if p != pe
|
143
|
+
if cs != 0
|
144
|
+
while true
|
145
|
+
_break_resume = false
|
146
|
+
begin
|
147
|
+
_break_again = false
|
148
|
+
_keys = _parser_key_offsets[cs]
|
149
|
+
_trans = _parser_index_offsets[cs]
|
150
|
+
_klen = _parser_single_lengths[cs]
|
151
|
+
_break_match = false
|
152
|
+
|
153
|
+
begin
|
154
|
+
if _klen > 0
|
155
|
+
_lower = _keys
|
156
|
+
_upper = _keys + _klen - 1
|
157
|
+
|
158
|
+
loop do
|
159
|
+
break if _upper < _lower
|
160
|
+
_mid = _lower + ( (_upper - _lower) >> 1 )
|
161
|
+
|
162
|
+
if data[p] < _parser_trans_keys[_mid]
|
163
|
+
_upper = _mid - 1
|
164
|
+
elsif data[p] > _parser_trans_keys[_mid]
|
165
|
+
_lower = _mid + 1
|
166
|
+
else
|
167
|
+
_trans += (_mid - _keys)
|
168
|
+
_break_match = true
|
169
|
+
break
|
170
|
+
end
|
171
|
+
end # loop
|
172
|
+
break if _break_match
|
173
|
+
_keys += _klen
|
174
|
+
_trans += _klen
|
175
|
+
end
|
176
|
+
_klen = _parser_range_lengths[cs]
|
177
|
+
if _klen > 0
|
178
|
+
_lower = _keys
|
179
|
+
_upper = _keys + (_klen << 1) - 2
|
180
|
+
loop do
|
181
|
+
break if _upper < _lower
|
182
|
+
_mid = _lower + (((_upper-_lower) >> 1) & ~1)
|
183
|
+
if data[p] < _parser_trans_keys[_mid]
|
184
|
+
_upper = _mid - 2
|
185
|
+
elsif data[p] > _parser_trans_keys[_mid+1]
|
186
|
+
_lower = _mid + 2
|
187
|
+
else
|
188
|
+
_trans += ((_mid - _keys) >> 1)
|
189
|
+
_break_match = true
|
190
|
+
break
|
191
|
+
end
|
192
|
+
end # loop
|
193
|
+
break if _break_match
|
194
|
+
_trans += _klen
|
195
|
+
end
|
196
|
+
end while false
|
197
|
+
cs = _parser_trans_targs_wi[_trans]
|
198
|
+
break if _parser_trans_actions_wi[_trans] == 0
|
199
|
+
_acts = _parser_trans_actions_wi[_trans]
|
200
|
+
_nacts = _parser_actions[_acts]
|
201
|
+
_acts += 1
|
202
|
+
while _nacts > 0
|
203
|
+
_nacts -= 1
|
204
|
+
_acts += 1
|
205
|
+
case _parser_actions[_acts - 1]
|
206
|
+
when 0:
|
207
|
+
# line 13 "lib/keyword_search.rl"
|
208
|
+
begin
|
209
|
+
|
210
|
+
tokstart = p;
|
211
|
+
end
|
212
|
+
# line 13 "lib/keyword_search.rl"
|
213
|
+
when 1:
|
214
|
+
# line 17 "lib/keyword_search.rl"
|
215
|
+
begin
|
216
|
+
|
217
|
+
key = data[tokstart...p-1]
|
218
|
+
results[key] ||= []
|
219
|
+
end
|
220
|
+
# line 17 "lib/keyword_search.rl"
|
221
|
+
when 2:
|
222
|
+
# line 22 "lib/keyword_search.rl"
|
223
|
+
begin
|
224
|
+
|
225
|
+
key = nil
|
226
|
+
end
|
227
|
+
# line 22 "lib/keyword_search.rl"
|
228
|
+
when 3:
|
229
|
+
# line 26 "lib/keyword_search.rl"
|
230
|
+
begin
|
231
|
+
|
232
|
+
value = data[tokstart..p-1]
|
233
|
+
value = value[1..-2] if ["'", '"'].include?(value[0,1])
|
234
|
+
(results[key || :default] ||= []) << value
|
235
|
+
end
|
236
|
+
# line 26 "lib/keyword_search.rl"
|
237
|
+
when 4:
|
238
|
+
# line 42 "lib/keyword_search.rl"
|
239
|
+
begin
|
240
|
+
raise ParseError, "At offset #{p}, near: '#{data[p,10]}'" end
|
241
|
+
# line 42 "lib/keyword_search.rl"
|
242
|
+
# line 244 "lib/keyword_search.rb"
|
243
|
+
end # action switch
|
244
|
+
end
|
245
|
+
end while false
|
246
|
+
break if _break_resume
|
247
|
+
break if cs == 0
|
248
|
+
p += 1
|
249
|
+
break if p == pe
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
254
|
+
# line 69 "lib/keyword_search.rl"
|
255
|
+
|
256
|
+
# line 258 "lib/keyword_search.rb"
|
257
|
+
# line 70 "lib/keyword_search.rl"
|
258
|
+
results
|
28
259
|
end
|
260
|
+
|
29
261
|
end
|
30
262
|
|
31
|
-
end
|
263
|
+
end
|
264
|
+
|
265
|
+
|
@@ -0,0 +1,85 @@
|
|
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 = data[tokstart...p-1]
|
19
|
+
results[key] ||= []
|
20
|
+
}
|
21
|
+
|
22
|
+
action default {
|
23
|
+
key = nil
|
24
|
+
}
|
25
|
+
|
26
|
+
action value {
|
27
|
+
value = data[tokstart..p-1]
|
28
|
+
value = value[1..-2] if ["'", '"'].include?(value[0,1])
|
29
|
+
(results[key || :default] ||= []) << value
|
30
|
+
}
|
31
|
+
|
32
|
+
action quote { quotes += 1 }
|
33
|
+
|
34
|
+
action unquote { quotes -= 1 }
|
35
|
+
|
36
|
+
bareword = [^ '":] [^ ":]*; # allow apostrophes
|
37
|
+
dquoted = '"' @ quote any* :>> '"' @ unquote;
|
38
|
+
squoted = '\'' @ quote any* :>> '\'' @ unquote;
|
39
|
+
|
40
|
+
value = ( dquoted | squoted | bareword );
|
41
|
+
|
42
|
+
pair = (bareword > start ':') % key value > start % value ;
|
43
|
+
|
44
|
+
definition = ( pair | value > start > default % value) ' ';
|
45
|
+
main := definition** 0
|
46
|
+
@!{ raise ParseError, "At offset #{p}, near: '#{data[p,10]}'" };
|
47
|
+
|
48
|
+
}%%
|
49
|
+
|
50
|
+
def search(input_string, definition=nil, &block)
|
51
|
+
definition ||= Definition.new(&block)
|
52
|
+
results = parse(input_string)
|
53
|
+
results.each do |key, terms|
|
54
|
+
definition.handle(key, terms)
|
55
|
+
end
|
56
|
+
results
|
57
|
+
end
|
58
|
+
|
59
|
+
#######
|
60
|
+
private
|
61
|
+
#######
|
62
|
+
|
63
|
+
def parse(input) #:nodoc:
|
64
|
+
data = input + ' '
|
65
|
+
%% write data;
|
66
|
+
p = 0
|
67
|
+
pe = data.length
|
68
|
+
key = nil
|
69
|
+
tokstart = nil
|
70
|
+
results = {}
|
71
|
+
quotes = 0
|
72
|
+
%% write init;
|
73
|
+
%% write exec;
|
74
|
+
%% write eof;
|
75
|
+
unless quotes.zero?
|
76
|
+
raise ParseError, "Unclosed quotes"
|
77
|
+
end
|
78
|
+
results
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
|
@@ -4,28 +4,29 @@ module KeywordSearch
|
|
4
4
|
|
5
5
|
class Keyword
|
6
6
|
|
7
|
-
attr_reader :name, :description
|
8
|
-
|
7
|
+
attr_reader :name, :description, :handler
|
9
8
|
def initialize(name, description=nil, &handler)
|
10
9
|
@name, @description = name, description
|
11
10
|
@handler = handler
|
12
11
|
end
|
13
12
|
|
14
13
|
def handle(value)
|
15
|
-
|
14
|
+
handler.call(value)
|
16
15
|
end
|
17
16
|
|
18
17
|
end
|
19
18
|
|
20
|
-
attr_reader :keywords
|
21
19
|
def initialize
|
22
|
-
@keywords = []
|
23
20
|
@default_keyword = nil
|
24
21
|
yield self if block_given?
|
25
22
|
end
|
23
|
+
|
24
|
+
def keywords
|
25
|
+
@keywords ||= []
|
26
|
+
end
|
26
27
|
|
27
28
|
def keyword(name, description=nil, &block)
|
28
|
-
|
29
|
+
keywords << Keyword.new(name, description, &block)
|
29
30
|
end
|
30
31
|
|
31
32
|
def default_keyword(name)
|
@@ -35,7 +36,7 @@ module KeywordSearch
|
|
35
36
|
def handle(key, values)
|
36
37
|
key = @default_keyword if key == :default
|
37
38
|
return false unless key
|
38
|
-
if k =
|
39
|
+
if k = keywords.detect { |kw| kw.name == key.to_sym}
|
39
40
|
k.handle(values)
|
40
41
|
end
|
41
42
|
end
|
data/test/test_keyword_search.rb
CHANGED
@@ -1,7 +1,11 @@
|
|
1
1
|
require 'test/unit'
|
2
|
+
|
3
|
+
require 'rubygems' rescue nil
|
4
|
+
require 'test/spec'
|
5
|
+
|
2
6
|
require File.dirname(__FILE__) + '/../lib/keyword_search'
|
3
7
|
|
4
|
-
|
8
|
+
context "KeywordSearch" do
|
5
9
|
|
6
10
|
NAME_AND_AGE = %<bruce williams age:26>
|
7
11
|
NAME_QUOTED_AND_AGE = %<"bruce williams" age:26>
|
@@ -10,7 +14,7 @@ class TestKeywordSearch < Test::Unit::TestCase
|
|
10
14
|
DEFAULT_AGE_WITH_SINGLE_QUOTED_AGE = %<26 name:'bruce williams'>
|
11
15
|
NAME_WITH_NESTED_SINGLE_QUOTES = %<"d'arcy d'uberville" age:28>
|
12
16
|
|
13
|
-
|
17
|
+
specify "default keyword" do
|
14
18
|
result = nil
|
15
19
|
KeywordSearch.search(NAME_AND_AGE) do |with|
|
16
20
|
with.default_keyword :name
|
@@ -21,7 +25,7 @@ class TestKeywordSearch < Test::Unit::TestCase
|
|
21
25
|
assert_equal 'bruce williams', result
|
22
26
|
end
|
23
27
|
|
24
|
-
|
28
|
+
specify "unquoted keyword term" do
|
25
29
|
result = nil
|
26
30
|
KeywordSearch.search(NAME_AND_AGE) do |with|
|
27
31
|
with.keyword :age do |values|
|
@@ -31,7 +35,7 @@ class TestKeywordSearch < Test::Unit::TestCase
|
|
31
35
|
assert_equal 26, result
|
32
36
|
end
|
33
37
|
|
34
|
-
|
38
|
+
specify "quoted default keyword term" do
|
35
39
|
result = nil
|
36
40
|
KeywordSearch.search(NAME_QUOTED_AND_AGE) do |with|
|
37
41
|
with.default_keyword :name
|
@@ -42,7 +46,7 @@ class TestKeywordSearch < Test::Unit::TestCase
|
|
42
46
|
assert_equal 'bruce williams', result
|
43
47
|
end
|
44
48
|
|
45
|
-
|
49
|
+
specify "quoted keyword term" do
|
46
50
|
result = nil
|
47
51
|
KeywordSearch.search(NAME_AND_QUOTED_AGE) do |with|
|
48
52
|
with.keyword :age do |values|
|
@@ -52,7 +56,7 @@ class TestKeywordSearch < Test::Unit::TestCase
|
|
52
56
|
assert_equal 26, result
|
53
57
|
end
|
54
58
|
|
55
|
-
|
59
|
+
specify "quoted keyword term with whitespace" do
|
56
60
|
result = nil
|
57
61
|
KeywordSearch.search(DEFAULT_AGE_WITH_QUOTED_AGE) do |with|
|
58
62
|
with.default_keyword :age
|
@@ -63,9 +67,9 @@ class TestKeywordSearch < Test::Unit::TestCase
|
|
63
67
|
assert_equal 'bruce williams', result
|
64
68
|
end
|
65
69
|
|
66
|
-
|
67
|
-
result = nil
|
68
|
-
KeywordSearch.search(DEFAULT_AGE_WITH_SINGLE_QUOTED_AGE) do |with|
|
70
|
+
specify "single quoted keyword term with whitespace" do
|
71
|
+
result = nil
|
72
|
+
r = KeywordSearch.search(DEFAULT_AGE_WITH_SINGLE_QUOTED_AGE) do |with|
|
69
73
|
with.default_keyword :age
|
70
74
|
with.keyword :name do |values|
|
71
75
|
result = values.first
|
@@ -74,7 +78,7 @@ class TestKeywordSearch < Test::Unit::TestCase
|
|
74
78
|
assert_equal 'bruce williams', result
|
75
79
|
end
|
76
80
|
|
77
|
-
|
81
|
+
specify "nested single quote is accumulated" do
|
78
82
|
result = nil
|
79
83
|
KeywordSearch.search(NAME_WITH_NESTED_SINGLE_QUOTES) do |with|
|
80
84
|
with.default_keyword :name
|
@@ -85,7 +89,7 @@ class TestKeywordSearch < Test::Unit::TestCase
|
|
85
89
|
assert_equal %<d'arcy d'uberville>, result
|
86
90
|
end
|
87
91
|
|
88
|
-
|
92
|
+
specify "nested double quote is accumulated" do
|
89
93
|
result = nil
|
90
94
|
KeywordSearch.search(%<'he was called "jake"'>) do |with|
|
91
95
|
with.default_keyword :text
|
@@ -96,7 +100,7 @@ class TestKeywordSearch < Test::Unit::TestCase
|
|
96
100
|
assert_equal %<he was called "jake">, result
|
97
101
|
end
|
98
102
|
|
99
|
-
|
103
|
+
specify "bare single quote in unquoted literal is accumulated" do
|
100
104
|
result = nil
|
101
105
|
KeywordSearch.search(%<bruce's age:27>) do |with|
|
102
106
|
with.default_keyword :text
|
@@ -107,7 +111,7 @@ class TestKeywordSearch < Test::Unit::TestCase
|
|
107
111
|
assert_equal %<bruce's>, result
|
108
112
|
end
|
109
113
|
|
110
|
-
|
114
|
+
specify "single quoted literal is accumulated" do
|
111
115
|
result = nil
|
112
116
|
KeywordSearch.search(%<foo 'bruce williams' age:27>) do |with|
|
113
117
|
with.default_keyword :text
|
@@ -118,7 +122,7 @@ class TestKeywordSearch < Test::Unit::TestCase
|
|
118
122
|
assert_equal %<bruce williams>, result
|
119
123
|
end
|
120
124
|
|
121
|
-
|
125
|
+
specify "period in literal is accumulated" do
|
122
126
|
result = nil
|
123
127
|
KeywordSearch.search(%<okay... age:27>) do |with|
|
124
128
|
with.default_keyword :text
|
@@ -129,7 +133,7 @@ class TestKeywordSearch < Test::Unit::TestCase
|
|
129
133
|
assert_equal %<okay...>, result
|
130
134
|
end
|
131
135
|
|
132
|
-
|
136
|
+
specify "parse error results in exception" do
|
133
137
|
assert_raises(KeywordSearch::ParseError) do
|
134
138
|
KeywordSearch.search(%<we_do_not_allow:! or ::>) do |with|
|
135
139
|
with.default_keyword :text
|
@@ -140,6 +144,38 @@ class TestKeywordSearch < Test::Unit::TestCase
|
|
140
144
|
end
|
141
145
|
end
|
142
146
|
|
147
|
+
specify "can use apostrophes in unquoted literal" do
|
148
|
+
result = nil
|
149
|
+
KeywordSearch.search(%<d'correct>) do |with|
|
150
|
+
with.default_keyword :text
|
151
|
+
with.keyword :text do |values|
|
152
|
+
result = values.first
|
153
|
+
end
|
154
|
+
end
|
155
|
+
assert_equal "d'correct", result
|
156
|
+
end
|
157
|
+
|
158
|
+
specify "can use apostrophes in unquoted literal values" do
|
159
|
+
result = nil
|
160
|
+
KeywordSearch.search(%<text:d'correct>) do |with|
|
161
|
+
with.default_keyword :text
|
162
|
+
with.keyword :text do |values|
|
163
|
+
result = values.first
|
164
|
+
end
|
165
|
+
end
|
166
|
+
assert_equal "d'correct", result
|
167
|
+
end
|
168
|
+
|
169
|
+
specify "cannot use an apostrophe at the beginning on an unquoted literal" do
|
170
|
+
assert_raises(KeywordSearch::ParseError) do
|
171
|
+
KeywordSearch.search(%<'thisiswrong>) do |with|
|
172
|
+
with.default_keyword :text
|
173
|
+
with.keyword :text do |values|
|
174
|
+
result = values.first
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
143
179
|
end
|
144
180
|
|
145
181
|
|
metadata
CHANGED
@@ -1,17 +1,17 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
|
-
rubygems_version: 0.9.
|
2
|
+
rubygems_version: 0.9.4
|
3
3
|
specification_version: 1
|
4
4
|
name: keyword_search
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 1.
|
7
|
-
date: 2007-
|
6
|
+
version: 1.3.0
|
7
|
+
date: 2007-10-09 00:00:00 -05:00
|
8
8
|
summary: Generic support for extracting GMail-style search keywords/values from strings
|
9
9
|
require_paths:
|
10
10
|
- lib
|
11
11
|
email: bruce@codefluency.com
|
12
12
|
homepage: " http://codefluency.rubyforge.org/keyword_search"
|
13
13
|
rubyforge_project: codefluency
|
14
|
-
description: "== FEATURES/PROBLEMS: The library features a very simple, easy-to-use API. * Define handlers for supported keywords with blocks * Define the default keyword (values not part of a keyword/value pair) Various notes: * Quoted values are supported. * Input is automatically downcased (both keywords and values should be assumed to be in lowercase) Development Roadmap:
|
14
|
+
description: "== FEATURES/PROBLEMS: The library features a very simple, easy-to-use API. * Define handlers for supported keywords with blocks * Define the default keyword (values not part of a keyword/value pair) Various notes: * Quoted values are supported. * Input is automatically downcased (both keywords and values should be assumed to be in lowercase) Development Roadmap: 2.0:: Add negation and grouping (will break API backwards compatibility) == SYNOPSIS:"
|
15
15
|
autorequire:
|
16
16
|
default_executable:
|
17
17
|
bindir: bin
|
@@ -34,18 +34,18 @@ files:
|
|
34
34
|
- README.txt
|
35
35
|
- Rakefile
|
36
36
|
- lib/keyword_search.rb
|
37
|
+
- lib/keyword_search.rl
|
37
38
|
- lib/keyword_search/definition.rb
|
38
|
-
- lib/keyword_search/evaluator.rb
|
39
|
-
- lib/keyword_search/grammar.rb
|
40
|
-
- lib/keyword_search/parser.rb
|
41
|
-
- lib/keyword_search/tokenizer.rb
|
42
39
|
- test/test_keyword_search.rb
|
43
40
|
test_files:
|
44
41
|
- test/test_keyword_search.rb
|
45
|
-
rdoc_options:
|
46
|
-
|
47
|
-
|
48
|
-
|
42
|
+
rdoc_options:
|
43
|
+
- --main
|
44
|
+
- README.txt
|
45
|
+
extra_rdoc_files:
|
46
|
+
- History.txt
|
47
|
+
- Manifest.txt
|
48
|
+
- README.txt
|
49
49
|
executables: []
|
50
50
|
|
51
51
|
extensions: []
|
@@ -53,15 +53,6 @@ extensions: []
|
|
53
53
|
requirements: []
|
54
54
|
|
55
55
|
dependencies:
|
56
|
-
- !ruby/object:Gem::Dependency
|
57
|
-
name: dhaka
|
58
|
-
version_requirement:
|
59
|
-
version_requirements: !ruby/object:Gem::Version::Requirement
|
60
|
-
requirements:
|
61
|
-
- - "="
|
62
|
-
- !ruby/object:Gem::Version
|
63
|
-
version: 2.1.0
|
64
|
-
version:
|
65
56
|
- !ruby/object:Gem::Dependency
|
66
57
|
name: hoe
|
67
58
|
version_requirement:
|
@@ -69,5 +60,5 @@ dependencies:
|
|
69
60
|
requirements:
|
70
61
|
- - ">="
|
71
62
|
- !ruby/object:Gem::Version
|
72
|
-
version: 1.
|
63
|
+
version: 1.3.0
|
73
64
|
version:
|
@@ -1,35 +0,0 @@
|
|
1
|
-
module KeywordSearch
|
2
|
-
|
3
|
-
class Evaluator < Dhaka::Evaluator
|
4
|
-
|
5
|
-
self.grammar = Grammar
|
6
|
-
|
7
|
-
define_evaluation_rules do
|
8
|
-
|
9
|
-
for_multiple_pairs do
|
10
|
-
child_nodes.inject({}) do |result,child_node|
|
11
|
-
evaluate(child_node).each do |key,value|
|
12
|
-
result[key] ||= []
|
13
|
-
result[key] += value
|
14
|
-
end
|
15
|
-
result
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
for_one_pair do
|
20
|
-
evaluate(child_nodes.first)
|
21
|
-
end
|
22
|
-
|
23
|
-
for_keyword_and_term do
|
24
|
-
{child_nodes.first.tokens.first.value => [child_nodes.last.tokens.first.value]}
|
25
|
-
end
|
26
|
-
|
27
|
-
for_default_keyword_term do
|
28
|
-
{:default => [child_nodes[0].tokens[0].value]}
|
29
|
-
end
|
30
|
-
|
31
|
-
end
|
32
|
-
|
33
|
-
end
|
34
|
-
|
35
|
-
end
|
@@ -1,21 +0,0 @@
|
|
1
|
-
module KeywordSearch
|
2
|
-
|
3
|
-
class Grammar < Dhaka::Grammar
|
4
|
-
|
5
|
-
for_symbol(Dhaka::START_SYMBOL_NAME) do
|
6
|
-
start ['Pairs']
|
7
|
-
end
|
8
|
-
|
9
|
-
for_symbol 'Pairs' do
|
10
|
-
one_pair ['Pair']
|
11
|
-
multiple_pairs ['Pairs', 'Pair']
|
12
|
-
end
|
13
|
-
|
14
|
-
for_symbol 'Pair' do
|
15
|
-
keyword_and_term ['s', ':', 's']
|
16
|
-
default_keyword_term ['s']
|
17
|
-
end
|
18
|
-
|
19
|
-
end
|
20
|
-
|
21
|
-
end
|
@@ -1,40 +0,0 @@
|
|
1
|
-
class KeywordSearch::Parser < Dhaka::CompiledParser
|
2
|
-
|
3
|
-
self.grammar = KeywordSearch::Grammar
|
4
|
-
|
5
|
-
start_with 0
|
6
|
-
|
7
|
-
at_state(1) {
|
8
|
-
for_symbols("_End_") { reduce_with "start" }
|
9
|
-
for_symbols("s") { shift_to 3 }
|
10
|
-
for_symbols("Pair") { shift_to 2 }
|
11
|
-
}
|
12
|
-
|
13
|
-
at_state(5) {
|
14
|
-
for_symbols("_End_", "s") { reduce_with "keyword_and_term" }
|
15
|
-
}
|
16
|
-
|
17
|
-
at_state(4) {
|
18
|
-
for_symbols("s") { shift_to 5 }
|
19
|
-
}
|
20
|
-
|
21
|
-
at_state(2) {
|
22
|
-
for_symbols("_End_", "s") { reduce_with "multiple_pairs" }
|
23
|
-
}
|
24
|
-
|
25
|
-
at_state(0) {
|
26
|
-
for_symbols("Pair") { shift_to 6 }
|
27
|
-
for_symbols("s") { shift_to 3 }
|
28
|
-
for_symbols("Pairs") { shift_to 1 }
|
29
|
-
}
|
30
|
-
|
31
|
-
at_state(6) {
|
32
|
-
for_symbols("_End_", "s") { reduce_with "one_pair" }
|
33
|
-
}
|
34
|
-
|
35
|
-
at_state(3) {
|
36
|
-
for_symbols(":") { shift_to 4 }
|
37
|
-
for_symbols("_End_", "s") { reduce_with "default_keyword_term" }
|
38
|
-
}
|
39
|
-
|
40
|
-
end
|
File without changes
|