keyword_search 1.2.0 → 1.3.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.
- 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
|