re2 0.3.0 → 0.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,100 @@
1
+ # re2 (http://github.com/mudge/re2)
2
+ # Ruby bindings to re2, an "efficient, principled regular expression library"
3
+ #
4
+ # Copyright (c) 2010-2012, Paul Mucur (http://mudge.name)
5
+ # Released under the BSD Licence, please see LICENSE.txt
6
+
7
+ require "re2"
8
+
9
+ module RE2
10
+ module String
11
+
12
+ # Replaces the first occurrence +pattern+ with +rewrite+ <i>in place</i>.
13
+ #
14
+ # @param [String, RE2::Regexp] pattern a regexp matching text to be replaced
15
+ # @param [String] rewrite the string to replace with
16
+ # @example
17
+ # "hello there".re2_sub("hello", "howdy") #=> "howdy there"
18
+ # re2 = RE2.new("hel+o")
19
+ # "hello there".re2_sub(re2, "yo") #=> "yo there"
20
+ # text = "Good morning"
21
+ # text.re2_sub("morn", "even") #=> "Good evening"
22
+ # text #=> "Good evening"
23
+ def re2_sub(*args)
24
+ RE2.Replace(self, *args)
25
+ end
26
+
27
+ # Replaces every occurrence of +pattern+ with +rewrite+ <i>in place</i>.
28
+ #
29
+ # @param [String, RE2::Regexp] pattern a regexp matching text to be replaced
30
+ # @param [String] rewrite the string to replace with
31
+ # @example
32
+ # "hello there".re2_gsub("e", "i") #=> "hillo thiri"
33
+ # re2 = RE2.new("oo?")
34
+ # "whoops-doops".re2_gsub(re2, "e") #=> "wheps-deps"
35
+ # text = "Good morning"
36
+ # text.re2_gsub("o", "ee") #=> "Geeeed meerning"
37
+ # text #=> "Geeeed meerning"
38
+ def re2_gsub(*args)
39
+ RE2.GlobalReplace(self, *args)
40
+ end
41
+
42
+ # Match the pattern and return either a boolean (if no submatches are required)
43
+ # or a {RE2::MatchData} instance.
44
+ #
45
+ # @return [Boolean, RE2::MatchData]
46
+ #
47
+ # @overload match(pattern)
48
+ # Returns an {RE2::MatchData} containing the matching
49
+ # pattern and all subpatterns resulting from looking for
50
+ # +pattern+.
51
+ #
52
+ # @param [String, RE2::Regexp] pattern the regular expression to match
53
+ # @return [RE2::MatchData] the matches
54
+ # @raise [NoMemoryError] if there was not enough memory to allocate the matches
55
+ # @example
56
+ # r = RE2::Regexp.new('w(o)(o)')
57
+ # "woo".re2_match(r) #=> #<RE2::MatchData "woo" 1:"o" 2:"o">
58
+ #
59
+ # @overload match(pattern, 0)
60
+ # Returns either true or false indicating whether a
61
+ # successful match was made.
62
+ #
63
+ # @param [String, RE2::Regexp] pattern the regular expression to match
64
+ # @return [Boolean] whether the match was successful
65
+ # @raise [NoMemoryError] if there was not enough memory to allocate the matches
66
+ # @example
67
+ # r = RE2::Regexp.new('w(o)(o)')
68
+ # "woo".re2_match(0) #=> true
69
+ # "bob".re2_match(0) #=> false
70
+ #
71
+ # @overload match(pattern, number_of_matches)
72
+ # See +match(pattern)+ but with a specific number of
73
+ # matches returned (padded with nils if necessary).
74
+ #
75
+ # @param [String, RE2::Regexp] pattern the regular expression to match
76
+ # @param [Fixnum] number_of_matches the number of matches to return
77
+ # @return [RE2::MatchData] the matches
78
+ # @raise [NoMemoryError] if there was not enough memory to allocate the matches
79
+ # @example
80
+ # r = RE2::Regexp.new('w(o)(o)')
81
+ # "woo".re2_match(r, 1) #=> #<RE2::MatchData "woo" 1:"o">
82
+ # "woo".re2_match(r, 3) #=> #<RE2::MatchData "woo" 1:"o" 2:"o" 3:nil>
83
+ def re2_match(pattern, *args)
84
+ RE2::Regexp.new(pattern).match(self, *args)
85
+ end
86
+
87
+ # Escapes all potentially meaningful regexp characters.
88
+ # The returned string, used as a regular expression, will exactly match the
89
+ # original string.
90
+ #
91
+ # @return [String] the escaped string
92
+ # @example
93
+ # "1.5-2.0?".escape #=> "1\.5\-2\.0\?"
94
+ def re2_escape
95
+ RE2.QuoteMeta(self)
96
+ end
97
+
98
+ alias_method :re2_quote, :re2_escape
99
+ end
100
+ end
@@ -0,0 +1,15 @@
1
+ require "spec_helper"
2
+
3
+ describe Kernel do
4
+ describe "#RE2" do
5
+ it "returns an RE2::Regexp instance given a pattern" do
6
+ RE2('w(o)(o)').must_be_instance_of(RE2::Regexp)
7
+ end
8
+
9
+ it "returns an RE2::Regexp instance given a pattern and options" do
10
+ re = RE2('w(o)(o)', :case_sensitive => false)
11
+ re.must_be_instance_of(RE2::Regexp)
12
+ re.wont_be(:case_sensitive?)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,141 @@
1
+ require "spec_helper"
2
+
3
+ describe RE2::MatchData do
4
+
5
+ describe "#to_a" do
6
+ it "is populated with the match and capturing groups" do
7
+ a = RE2::Regexp.new('w(o)(o)').match('woo').to_a
8
+ a.must_equal(["woo", "o", "o"])
9
+ end
10
+
11
+ it "populates optional capturing groups with nil if they are missing" do
12
+ a = RE2::Regexp.new('(\d?)(a)(b)').match('ab').to_a
13
+ a.must_equal(["ab", nil, "a", "b"])
14
+ end
15
+ end
16
+
17
+ describe "#[]" do
18
+ it "accesses capturing groups by numerical index" do
19
+ md = RE2::Regexp.new('(\d)(\d{2})').match("123")
20
+ md[1].must_equal("1")
21
+ md[2].must_equal("23")
22
+ end
23
+
24
+ it "has the whole match as the 0th item" do
25
+ md = RE2::Regexp.new('(\d)(\d{2})').match("123")
26
+ md[0].must_equal("123")
27
+ end
28
+
29
+ it "supports access by numerical ranges" do
30
+ md = RE2::Regexp.new('(\d+) (\d+) (\d+)').match("123 456 789")
31
+ md[1..3].must_equal(["123", "456", "789"])
32
+ md[1...3].must_equal(["123", "456"])
33
+ end
34
+
35
+ it "supports slicing" do
36
+ md = RE2::Regexp.new('(\d+) (\d+) (\d+)').match("123 456 789")
37
+ md[1, 3].must_equal(["123", "456", "789"])
38
+ md[1, 2].must_equal(["123", "456"])
39
+ end
40
+
41
+ it "returns nil if attempting to access non-existent capturing groups by index" do
42
+ md = RE2::Regexp.new('(\d+)').match('bob 123')
43
+ md[2].must_be_nil
44
+ md[3].must_be_nil
45
+ end
46
+
47
+ it "allows access by string names when there are named groups" do
48
+ md = RE2::Regexp.new('(?P<numbers>\d+)').match('bob 123')
49
+ md["numbers"].must_equal("123")
50
+ end
51
+
52
+ it "allows access by symbol names when there are named groups" do
53
+ md = RE2::Regexp.new('(?P<numbers>\d+)').match('bob 123')
54
+ md[:numbers].must_equal("123")
55
+ end
56
+
57
+ it "allows access by names and indices with mixed groups" do
58
+ md = RE2::Regexp.new('(?P<name>\w+)(\s*)(?P<numbers>\d+)').match("bob 123")
59
+ md["name"].must_equal("bob")
60
+ md[:name].must_equal("bob")
61
+ md[2].must_equal(" ")
62
+ md["numbers"].must_equal("123")
63
+ md[:numbers].must_equal("123")
64
+ end
65
+
66
+ it "returns nil if no such named group exists" do
67
+ md = RE2::Regexp.new('(\d+)').match("bob 123")
68
+ md["missing"].must_be_nil
69
+ md[:missing].must_be_nil
70
+ end
71
+ end
72
+
73
+ describe "#string" do
74
+ it "returns the original string to match against" do
75
+ re = RE2::Regexp.new('(\D+)').match("bob")
76
+ re.string.must_equal("bob")
77
+ end
78
+
79
+ it "returns a copy, not the actual original" do
80
+ string = "bob"
81
+ re = RE2::Regexp.new('(\D+)').match(string)
82
+ re.string.wont_be_same_as(string)
83
+ end
84
+
85
+ it "returns a frozen string" do
86
+ re = RE2::Regexp.new('(\D+)').match("bob")
87
+ re.string.must_be(:frozen?)
88
+ end
89
+ end
90
+
91
+ describe "#size" do
92
+ it "returns the number of capturing groups plus the matching string" do
93
+ md = RE2::Regexp.new('(\d+) (\d+)').match("1234 56")
94
+ md.size.must_equal(3)
95
+ end
96
+ end
97
+
98
+ describe "#length" do
99
+ it "returns the number of capturing groups plus the matching string" do
100
+ md = RE2::Regexp.new('(\d+) (\d+)').match("1234 56")
101
+ md.length.must_equal(3)
102
+ end
103
+ end
104
+
105
+ describe "#regexp" do
106
+ it "returns the original RE2::Regexp used" do
107
+ re = RE2::Regexp.new('(\d+)')
108
+ md = re.match("123")
109
+ md.regexp.must_be_same_as(re)
110
+ end
111
+ end
112
+
113
+ describe "#inspect" do
114
+ it "returns a text representation of the object and indices" do
115
+ md = RE2::Regexp.new('(\d+) (\d+)').match("1234 56")
116
+ md.inspect.must_equal('#<RE2::MatchData "1234 56" 1:"1234" 2:"56">')
117
+ end
118
+
119
+ it "represents missing matches as nil" do
120
+ md = RE2::Regexp.new('(\d+) (\d+)?').match("1234 ")
121
+ md.inspect.must_equal('#<RE2::MatchData "1234 " 1:"1234" 2:nil>')
122
+ end
123
+ end
124
+
125
+ describe "#to_s" do
126
+ it "returns the matching part of the original string" do
127
+ md = RE2::Regexp.new('(\d{2,5})').match("one two 23456")
128
+ md.to_s.must_equal("23456")
129
+ end
130
+ end
131
+
132
+ describe "#to_ary" do
133
+ it "allows the object to be expanded with an asterisk" do
134
+ md = RE2::Regexp.new('(\d+) (\d+)').match("1234 56")
135
+ m1, m2, m3 = *md
136
+ m1.must_equal("1234 56")
137
+ m2.must_equal("1234")
138
+ m3.must_equal("56")
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,394 @@
1
+ require "spec_helper"
2
+
3
+ describe RE2::Regexp do
4
+ describe "#initialize" do
5
+ it "returns an instance given only a pattern" do
6
+ re = RE2::Regexp.new('woo')
7
+ re.must_be_instance_of(RE2::Regexp)
8
+ end
9
+
10
+ it "returns an instance given a pattern and options" do
11
+ re = RE2::Regexp.new('woo', :case_sensitive => false)
12
+ re.must_be_instance_of(RE2::Regexp)
13
+ end
14
+ end
15
+
16
+ describe "#compile" do
17
+ it "returns an instance given only a pattern" do
18
+ re = RE2::Regexp.compile('woo')
19
+ re.must_be_instance_of(RE2::Regexp)
20
+ end
21
+
22
+ it "returns an instance given a pattern and options" do
23
+ re = RE2::Regexp.compile('woo', :case_sensitive => false)
24
+ re.must_be_instance_of(RE2::Regexp)
25
+ end
26
+ end
27
+
28
+ describe "#options" do
29
+ it "returns a hash of options" do
30
+ options = RE2::Regexp.new('woo').options
31
+ options.must_be_instance_of(Hash)
32
+ end
33
+
34
+ it "is populated with default options when nothing has been set" do
35
+ options = RE2::Regexp.new('woo').options
36
+ assert options[:utf8]
37
+ refute options[:posix_syntax]
38
+ refute options[:longest_match]
39
+ assert [:log_errors]
40
+ refute options[:literal]
41
+ refute options[:never_nl]
42
+ assert options[:case_sensitive]
43
+ refute options[:perl_classes]
44
+ refute options[:word_boundary]
45
+ refute options[:one_line]
46
+ end
47
+
48
+ it "is populated with overridden options when specified" do
49
+ options = RE2::Regexp.new('woo', :case_sensitive => false).options
50
+ refute options[:case_sensitive]
51
+ end
52
+ end
53
+
54
+ describe "#error" do
55
+ it "returns nil if there is no error" do
56
+ error = RE2::Regexp.new('woo').error
57
+ error.must_be_nil
58
+ end
59
+
60
+ # Use log_errors => false to suppress RE2's logging to STDERR.
61
+ it "contains the error string if there is an error" do
62
+ error = RE2::Regexp.new('wo(o', :log_errors => false).error
63
+ error.must_equal("missing ): wo(o")
64
+ end
65
+ end
66
+
67
+ describe "#error_arg" do
68
+ it "returns nil if there is no error" do
69
+ error_arg = RE2::Regexp.new('woo').error_arg
70
+ error_arg.must_be_nil
71
+ end
72
+
73
+ it "returns the offending portin of the regexp if there is an error" do
74
+ error_arg = RE2::Regexp.new('wo(o', :log_errors => false).error_arg
75
+ error_arg.must_equal("wo(o")
76
+ end
77
+ end
78
+
79
+ describe "#program_size" do
80
+ it "returns a numeric value" do
81
+ program_size = RE2::Regexp.new('w(o)(o)').program_size
82
+ program_size.must_be_instance_of(Fixnum)
83
+ end
84
+ end
85
+
86
+ describe "#to_str" do
87
+ it "returns the original pattern" do
88
+ string = RE2::Regexp.new('w(o)(o)').to_str
89
+ string.must_equal("w(o)(o)")
90
+ end
91
+ end
92
+
93
+ describe "#pattern" do
94
+ it "returns the original pattern" do
95
+ pattern = RE2::Regexp.new('w(o)(o)').pattern
96
+ pattern.must_equal("w(o)(o)")
97
+ end
98
+ end
99
+
100
+ describe "#inspect" do
101
+ it "shows the class name and original pattern" do
102
+ string = RE2::Regexp.new('w(o)(o)').inspect
103
+ string.must_equal("#<RE2::Regexp /w(o)(o)/>")
104
+ end
105
+ end
106
+
107
+ describe "#utf8?" do
108
+ it "returns true by default" do
109
+ RE2::Regexp.new('woo').must_be(:utf8?)
110
+ end
111
+
112
+ it "can be overridden on initialization" do
113
+ re = RE2::Regexp.new('woo', :utf8 => false)
114
+ re.wont_be(:utf8?)
115
+ end
116
+ end
117
+
118
+ describe "#posix_syntax?" do
119
+ it "returns false by default" do
120
+ RE2::Regexp.new('woo').wont_be(:posix_syntax?)
121
+ end
122
+
123
+ it "can be overridden on initialization" do
124
+ re = RE2::Regexp.new('woo', :posix_syntax => true)
125
+ re.must_be(:posix_syntax?)
126
+ end
127
+ end
128
+
129
+ describe "#literal?" do
130
+ it "returns false by default" do
131
+ RE2::Regexp.new('woo').wont_be(:literal?)
132
+ end
133
+
134
+ it "can be overridden on initialization" do
135
+ re = RE2::Regexp.new('woo', :literal => true)
136
+ re.must_be(:literal?)
137
+ end
138
+ end
139
+
140
+ describe "#never_nl?" do
141
+ it "returns false by default" do
142
+ RE2::Regexp.new('woo').wont_be(:never_nl?)
143
+ end
144
+
145
+ it "can be overridden on initialization" do
146
+ re = RE2::Regexp.new('woo', :never_nl => true)
147
+ re.must_be(:never_nl?)
148
+ end
149
+ end
150
+
151
+ describe "#case_sensitive?" do
152
+ it "returns true by default" do
153
+ RE2::Regexp.new('woo').must_be(:case_sensitive?)
154
+ end
155
+
156
+ it "can be overridden on initialization" do
157
+ re = RE2::Regexp.new('woo', :case_sensitive => false)
158
+ re.wont_be(:case_sensitive?)
159
+ end
160
+ end
161
+
162
+ describe "#case_insensitive?" do
163
+ it "returns false by default" do
164
+ RE2::Regexp.new('woo').wont_be(:case_insensitive?)
165
+ end
166
+
167
+ it "can be overridden on initialization" do
168
+ re = RE2::Regexp.new('woo', :case_sensitive => false)
169
+ re.must_be(:case_insensitive?)
170
+ end
171
+ end
172
+
173
+ describe "#casefold?" do
174
+ it "returns true by default" do
175
+ RE2::Regexp.new('woo').wont_be(:casefold?)
176
+ end
177
+
178
+ it "can be overridden on initialization" do
179
+ re = RE2::Regexp.new('woo', :case_sensitive => false)
180
+ re.must_be(:casefold?)
181
+ end
182
+ end
183
+
184
+ describe "#longest_match?" do
185
+ it "returns false by default" do
186
+ RE2::Regexp.new('woo').wont_be(:casefold?)
187
+ end
188
+
189
+ it "can be overridden on initialization" do
190
+ re = RE2::Regexp.new('woo', :longest_match => true)
191
+ re.must_be(:longest_match?)
192
+ end
193
+ end
194
+
195
+ describe "#log_errors?" do
196
+ it "returns true by default" do
197
+ RE2::Regexp.new('woo').must_be(:log_errors?)
198
+ end
199
+
200
+ it "can be overridden on initialization" do
201
+ re = RE2::Regexp.new('woo', :log_errors => false)
202
+ re.wont_be(:log_errors?)
203
+ end
204
+ end
205
+
206
+ describe "#perl_classes?" do
207
+ it "returns false by default" do
208
+ RE2::Regexp.new('woo').wont_be(:perl_classes?)
209
+ end
210
+
211
+ it "can be overridden on initialization" do
212
+ re = RE2::Regexp.new('woo', :perl_classes => true)
213
+ re.must_be(:perl_classes?)
214
+ end
215
+ end
216
+
217
+ describe "#word_boundary?" do
218
+ it "returns false by default" do
219
+ RE2::Regexp.new('woo').wont_be(:word_boundary?)
220
+ end
221
+
222
+ it "can be overridden on initialization" do
223
+ re = RE2::Regexp.new('woo', :word_boundary => true)
224
+ re.must_be(:word_boundary?)
225
+ end
226
+ end
227
+
228
+ describe "#one_line?" do
229
+ it "returns false by default" do
230
+ RE2::Regexp.new('woo').wont_be(:one_line?)
231
+ end
232
+
233
+ it "can be overridden on initialization" do
234
+ re = RE2::Regexp.new('woo', :one_line => true)
235
+ re.must_be(:one_line?)
236
+ end
237
+ end
238
+
239
+ describe "#max_mem" do
240
+ it "returns the default max memory" do
241
+ RE2::Regexp.new('woo').max_mem.must_equal(8388608)
242
+ end
243
+
244
+ it "can be overridden on initialization" do
245
+ re = RE2::Regexp.new('woo', :max_mem => 1024)
246
+ re.max_mem.must_equal(1024)
247
+ end
248
+ end
249
+
250
+ describe "#match" do
251
+ let(:re) { RE2::Regexp.new('My name is (\S+) (\S+)') }
252
+
253
+ it "returns match data given only text" do
254
+ md = re.match("My name is Robert Paulson")
255
+ md.must_be_instance_of(RE2::MatchData)
256
+ end
257
+
258
+ it "returns nil if there is no match for the given text" do
259
+ re.match("My age is 99").must_be_nil
260
+ end
261
+
262
+ it "returns only true or false if no matches are requested" do
263
+ re.match("My name is Robert Paulson", 0).must_equal(true)
264
+ re.match("My age is 99", 0).must_equal(false)
265
+ end
266
+
267
+ describe "with a specific number of matches under the total in the pattern" do
268
+ subject { re.match("My name is Robert Paulson", 1) }
269
+
270
+ it "returns a match data object" do
271
+ subject.must_be_instance_of(RE2::MatchData)
272
+ end
273
+
274
+ it "has the whole match and only the specified number of matches" do
275
+ subject.size.must_equal(2)
276
+ end
277
+
278
+ it "populates any specified matches" do
279
+ subject[1].must_equal("Robert")
280
+ end
281
+
282
+ it "does not populate any matches that weren't included" do
283
+ subject[2].must_be_nil
284
+ end
285
+ end
286
+
287
+ describe "with a number of matches over the total in the pattern" do
288
+ subject { re.match("My name is Robert Paulson", 5) }
289
+
290
+ it "returns a match data object" do
291
+ subject.must_be_instance_of(RE2::MatchData)
292
+ end
293
+
294
+ it "has the whole match the specified number of matches" do
295
+ subject.size.must_equal(6)
296
+ end
297
+
298
+ it "populates any specified matches" do
299
+ subject[1].must_equal("Robert")
300
+ subject[2].must_equal("Paulson")
301
+ end
302
+
303
+ it "pads the remaining matches with nil" do
304
+ subject[3].must_be_nil
305
+ subject[4].must_be_nil
306
+ subject[5].must_be_nil
307
+ subject[6].must_be_nil
308
+ end
309
+ end
310
+ end
311
+
312
+ describe "#match?" do
313
+ it "returns only true or false if no matches are requested" do
314
+ re = RE2::Regexp.new('My name is (\S+) (\S+)')
315
+ re.match?("My name is Robert Paulson").must_equal(true)
316
+ re.match?("My age is 99").must_equal(false)
317
+ end
318
+ end
319
+
320
+ describe "#=~" do
321
+ it "returns only true or false if no matches are requested" do
322
+ re = RE2::Regexp.new('My name is (\S+) (\S+)')
323
+ (re =~ "My name is Robert Paulson").must_equal(true)
324
+ (re =~ "My age is 99").must_equal(false)
325
+ end
326
+ end
327
+
328
+ describe "#!~" do
329
+ it "returns only true or false if no matches are requested" do
330
+ re = RE2::Regexp.new('My name is (\S+) (\S+)')
331
+ (re !~ "My name is Robert Paulson").must_equal(false)
332
+ (re !~ "My age is 99").must_equal(true)
333
+ end
334
+ end
335
+
336
+ describe "#===" do
337
+ it "returns only true or false if no matches are requested" do
338
+ re = RE2::Regexp.new('My name is (\S+) (\S+)')
339
+ (re === "My name is Robert Paulson").must_equal(true)
340
+ (re === "My age is 99").must_equal(false)
341
+ end
342
+ end
343
+
344
+ describe "#ok?" do
345
+ it "returns true for valid regexps" do
346
+ RE2::Regexp.new('woo').must_be(:ok?)
347
+ RE2::Regexp.new('wo(o)').must_be(:ok?)
348
+ RE2::Regexp.new('((\d)\w+){3,}').must_be(:ok?)
349
+ end
350
+
351
+ it "returns false for invalid regexps" do
352
+ RE2::Regexp.new('wo(o', :log_errors => false).wont_be(:ok?)
353
+ RE2::Regexp.new('wo[o', :log_errors => false).wont_be(:ok?)
354
+ RE2::Regexp.new('*', :log_errors => false).wont_be(:ok?)
355
+ end
356
+ end
357
+
358
+ describe "#escape" do
359
+ it "transforms a string into a regexp" do
360
+ RE2::Regexp.escape("1.5-2.0?").must_equal('1\.5\-2\.0\?')
361
+ end
362
+ end
363
+
364
+ describe "#quote" do
365
+ it "transforms a string into a regexp" do
366
+ RE2::Regexp.quote("1.5-2.0?").must_equal('1\.5\-2\.0\?')
367
+ end
368
+ end
369
+
370
+ describe "#number_of_capturing_groups" do
371
+ it "returns the number of groups in a regexp" do
372
+ RE2::Regexp.new('(a)(b)(c)').number_of_capturing_groups.must_equal(3)
373
+ RE2::Regexp.new('abc').number_of_capturing_groups.must_equal(0)
374
+ RE2::Regexp.new('a((b)c)').number_of_capturing_groups.must_equal(2)
375
+ end
376
+ end
377
+
378
+ describe "#named_capturing_groups" do
379
+ it "returns a hash of names to indices" do
380
+ RE2::Regexp.new('(?P<bob>a)').named_capturing_groups.must_be_instance_of(Hash)
381
+ end
382
+
383
+ it "maps names to indices with only one group" do
384
+ groups = RE2::Regexp.new('(?P<bob>a)').named_capturing_groups
385
+ groups["bob"].must_equal(1)
386
+ end
387
+
388
+ it "maps names to indices with several groups" do
389
+ groups = RE2::Regexp.new('(?P<bob>a)(o)(?P<rob>e)').named_capturing_groups
390
+ groups["bob"].must_equal(1)
391
+ groups["rob"].must_equal(3)
392
+ end
393
+ end
394
+ end