re2 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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