re2 2.27.0-aarch64-linux-gnu → 2.29.0-aarch64-linux-gnu

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/lib/re2/regexp.rb CHANGED
@@ -68,5 +68,48 @@ module RE2
68
68
  def full_match(text, options = {})
69
69
  match(text, Hash(options).merge(anchor: :anchor_both))
70
70
  end
71
+
72
+ # Match the pattern against any substring of the given `text`, returning
73
+ # `false` if it is not a `String`, `Symbol`, or object responding to
74
+ # `to_str` so that an {RE2::Regexp} can be used in a `case` statement like
75
+ # Ruby's own `Regexp#===`.
76
+ #
77
+ # @param [Object] text the text to search; anything other than a `String`,
78
+ # `Symbol`, or object responding to `to_str` returns `false`
79
+ # @return [Boolean] whether the match was successful
80
+ # @example
81
+ # case "bob 123"
82
+ # when RE2::Regexp.new('\d+')
83
+ # "numbers"
84
+ # end
85
+ def ===(text)
86
+ text = text.to_s if text.is_a?(Symbol)
87
+ text = ::String.try_convert(text)
88
+ return false unless text
89
+
90
+ match?(text)
91
+ end
92
+
93
+ # Return whether this regular expression has the same pattern and options
94
+ # as another {RE2::Regexp}.
95
+ #
96
+ # @param [Object] other the object to compare against
97
+ # @return [Boolean] whether the two regular expressions are equal
98
+ # @example
99
+ # RE2::Regexp.new('abc') == RE2::Regexp.new('abc') #=> true
100
+ # RE2::Regexp.new('abc') == RE2::Regexp.new('abc', case_sensitive: false) #=> false
101
+ def ==(other)
102
+ other.is_a?(RE2::Regexp) && source == other.source && options == other.options
103
+ end
104
+
105
+ alias_method :eql?, :==
106
+
107
+ # Return a hash based on the pattern and options so that equal
108
+ # {RE2::Regexp}s can be used as Hash keys.
109
+ #
110
+ # @return [Integer] the hash of the regular expression
111
+ def hash
112
+ [source, options].hash
113
+ end
71
114
  end
72
115
  end
data/lib/re2/version.rb CHANGED
@@ -10,5 +10,5 @@
10
10
 
11
11
 
12
12
  module RE2
13
- VERSION = "2.27.0"
13
+ VERSION = "2.29.0"
14
14
  end
data/lib/re2.rb CHANGED
@@ -16,5 +16,6 @@ rescue LoadError
16
16
  end
17
17
 
18
18
  require "re2/regexp"
19
+ require "re2/match_data"
19
20
  require "re2/scanner"
20
21
  require "re2/version"
data/re2.gemspec CHANGED
@@ -20,6 +20,7 @@ Gem::Specification.new do |s|
20
20
  "Gemfile",
21
21
  "lib/re2.rb",
22
22
  "lib/re2/regexp.rb",
23
+ "lib/re2/match_data.rb",
23
24
  "lib/re2/scanner.rb",
24
25
  "lib/re2/string.rb",
25
26
  "lib/re2/version.rb",
@@ -41,7 +42,7 @@ Gem::Specification.new do |s|
41
42
  "spec/re2/scanner_spec.rb"
42
43
  ]
43
44
  s.add_development_dependency("rake-compiler", "~> 1.3.1")
44
- s.add_development_dependency("rake-compiler-dock", "~> 1.11.0")
45
+ s.add_development_dependency("rake-compiler-dock", "~> 1.12.0")
45
46
  s.add_development_dependency("rspec", "~> 3.2")
46
47
  s.add_runtime_dependency("mini_portile2", "~> 2.8.9") # keep version in sync with extconf.rb
47
48
  end
@@ -59,6 +59,15 @@ RSpec.describe RE2::MatchData do
59
59
  end
60
60
  end
61
61
 
62
+ describe "#initialize_copy" do
63
+ it "is a no-op when the source is the same object" do
64
+ md = RE2::Regexp.new('(\d+)').match("123")
65
+ md.send(:initialize_copy, md)
66
+
67
+ expect(md.to_a).to eq(["123", "123"])
68
+ end
69
+ end
70
+
62
71
  describe "#to_a" do
63
72
  it "is populated with the match and capturing groups" do
64
73
  a = RE2::Regexp.new('w(o)(o)').match('woo').to_a
@@ -154,6 +163,24 @@ RSpec.describe RE2::MatchData do
154
163
  expect(md[:numbers]).to eq("123")
155
164
  end
156
165
 
166
+ it "allows access using a UTF-8 name and a UTF-8 pattern" do
167
+ re = RE2::Regexp.new('(?P<café>\d+)', log_errors: false)
168
+ skip "Underlying RE2 does not support non-ASCII capturing group names" unless re.ok?
169
+
170
+ md = re.match('bob 123')
171
+
172
+ expect(md[:café]).to eq("123")
173
+ end
174
+
175
+ it "allows access using a Latin-1 name and a Latin-1 pattern" do
176
+ re = RE2::Regexp.new('(?P<café>\d+)'.encode("ISO-8859-1"), utf8: false, log_errors: false)
177
+ skip "Underlying RE2 does not support non-ASCII capturing group names" unless re.ok?
178
+
179
+ md = re.match('bob 123')
180
+
181
+ expect(md["café".encode("ISO-8859-1")]).to eq("123")
182
+ end
183
+
157
184
  it "allows access by names and indices with mixed groups", :aggregate_failures do
158
185
  md = RE2::Regexp.new('(?P<name>\w+)(\s*)(?P<numbers>\d+)').match("bob 123")
159
186
 
@@ -199,6 +226,12 @@ RSpec.describe RE2::MatchData do
199
226
  expect(md[:name].encoding.name).to eq("ISO-8859-1")
200
227
  end
201
228
 
229
+ it "handles null bytes in symbol names" do
230
+ md = RE2::Regexp.new('(?P<name>\S+)').match("bob")
231
+
232
+ expect(md[:"name\0b"]).to be_nil
233
+ end
234
+
202
235
  it "supports GC compaction" do
203
236
  md = RE2::Regexp.new('(wo{2})').match('woohoo' * 5)
204
237
  GC.compact
@@ -358,6 +391,24 @@ RSpec.describe RE2::MatchData do
358
391
  expect(md.string[md.begin(:foo)..-1]).to eq('foobar')
359
392
  end
360
393
 
394
+ it "returns the offset of the start of a match by non-ASCII symbol name" do
395
+ re = RE2::Regexp.new('(?P<café>fo{2})', log_errors: false)
396
+ skip "Underlying RE2 does not support non-ASCII capturing group names" unless re.ok?
397
+
398
+ md = re.match('a foobar')
399
+
400
+ expect(md.string[md.begin(:café)..-1]).to eq('foobar')
401
+ end
402
+
403
+ it "returns the offset of the start of a match by Latin-1 string name" do
404
+ re = RE2::Regexp.new('(?P<café>fo{2})'.encode("ISO-8859-1"), utf8: false, log_errors: false)
405
+ skip "Underlying RE2 does not support non-ASCII capturing group names" unless re.ok?
406
+
407
+ md = re.match('a foobar')
408
+
409
+ expect(md.string[md.begin("café".encode("ISO-8859-1"))..-1]).to eq('foobar')
410
+ end
411
+
361
412
  it "returns the offset of the start of a match by something that can be coerced to a String" do
362
413
  md = RE2::Regexp.new('(?P<foo>fo{2})').match('a foobar')
363
414
 
@@ -581,6 +632,108 @@ RSpec.describe RE2::MatchData do
581
632
  end
582
633
  end
583
634
 
635
+ describe "#bytebegin" do
636
+ it "returns the byte offset of the start of a match by index" do
637
+ md = RE2::Regexp.new('(Ruby)').match("I ♥ Ruby")
638
+
639
+ expect(md.bytebegin(0)).to eq(6)
640
+ end
641
+
642
+ it "returns the byte offset of the start of a submatch by string name" do
643
+ md = RE2::Regexp.new('(?P<word>Ruby)').match("I ♥ Ruby")
644
+
645
+ expect(md.bytebegin("word")).to eq(6)
646
+ end
647
+
648
+ it "returns the byte offset of the start of a submatch by symbol name" do
649
+ md = RE2::Regexp.new('(?P<word>Ruby)').match("I ♥ Ruby")
650
+
651
+ expect(md.bytebegin(:word)).to eq(6)
652
+ end
653
+
654
+ it "returns nil for an unmatched submatch" do
655
+ md = RE2::Regexp.new('a(b)?').match("ac")
656
+
657
+ expect(md.bytebegin(1)).to be_nil
658
+ end
659
+
660
+ it "returns nil for a nonexistent submatch" do
661
+ md = RE2::Regexp.new('(a)').match("a")
662
+
663
+ expect(md.bytebegin(5)).to be_nil
664
+ end
665
+
666
+ it "raises an error when called on an uninitialized object" do
667
+ expect { described_class.allocate.bytebegin(0) }.to raise_error(TypeError, /uninitialized RE2::MatchData/)
668
+ end
669
+ end
670
+
671
+ describe "#byteend" do
672
+ it "returns the byte offset after the end of a match by index" do
673
+ md = RE2::Regexp.new('(Ruby)').match("I ♥ Ruby")
674
+
675
+ expect(md.byteend(0)).to eq(10)
676
+ end
677
+
678
+ it "returns the byte offset after the end of a submatch by string name" do
679
+ md = RE2::Regexp.new('(?P<heart>♥)').match("I ♥ Ruby")
680
+
681
+ expect(md.byteend("heart")).to eq(5)
682
+ end
683
+
684
+ it "returns the byte offset after the end of a submatch by symbol name" do
685
+ md = RE2::Regexp.new('(?P<heart>♥)').match("I ♥ Ruby")
686
+
687
+ expect(md.byteend(:heart)).to eq(5)
688
+ end
689
+
690
+ it "returns nil for an unmatched submatch" do
691
+ md = RE2::Regexp.new('a(b)?').match("ac")
692
+
693
+ expect(md.byteend(1)).to be_nil
694
+ end
695
+
696
+ it "raises an error when called on an uninitialized object" do
697
+ expect { described_class.allocate.byteend(0) }.to raise_error(TypeError, /uninitialized RE2::MatchData/)
698
+ end
699
+ end
700
+
701
+ describe "#byteoffset" do
702
+ it "returns the byte offsets of a match by index" do
703
+ md = RE2::Regexp.new('(Ruby)').match("I ♥ Ruby")
704
+
705
+ expect(md.byteoffset(0)).to eq([6, 10])
706
+ end
707
+
708
+ it "returns the byte offsets of a submatch by string name" do
709
+ md = RE2::Regexp.new('(?P<heart>♥)').match("I ♥ Ruby")
710
+
711
+ expect(md.byteoffset("heart")).to eq([2, 5])
712
+ end
713
+
714
+ it "returns the byte offsets of a submatch by symbol name" do
715
+ md = RE2::Regexp.new('(?P<heart>♥)').match("I ♥ Ruby")
716
+
717
+ expect(md.byteoffset(:heart)).to eq([2, 5])
718
+ end
719
+
720
+ it "can be used to slice the original string by bytes" do
721
+ md = RE2::Regexp.new('(Ruby)').match("I ♥ Ruby")
722
+
723
+ expect(md.string.byteslice(*md.byteoffset(1))).to eq("Ruby")
724
+ end
725
+
726
+ it "returns nil for an unmatched submatch" do
727
+ md = RE2::Regexp.new('a(b)?').match("ac")
728
+
729
+ expect(md.byteoffset(1)).to be_nil
730
+ end
731
+
732
+ it "raises an error when called on an uninitialized object" do
733
+ expect { described_class.allocate.byteoffset(0) }.to raise_error(TypeError, /uninitialized RE2::MatchData/)
734
+ end
735
+ end
736
+
584
737
  describe "#offset" do
585
738
  it "returns the offset of a match by index" do
586
739
  md = RE2::Regexp.new('ob (\d+)').match("bob 123")
@@ -822,12 +975,33 @@ RSpec.describe RE2::MatchData do
822
975
  expect(md.named_captures).to eq("empty" => "", "word" => "bob")
823
976
  end
824
977
 
978
+ it "returns UTF-8 names even for Latin-1 patterns" do
979
+ md = RE2::Regexp.new('(?P<numbers>\d+)', utf8: false).match('123')
980
+ key = md.named_captures.keys.first
981
+
982
+ expect(key.encoding).to eq(Encoding::UTF_8)
983
+ end
984
+
825
985
  it "returns symbol keys when symbolize_names: true" do
826
986
  md = RE2::Regexp.new('(?P<numbers>\d+) (?P<letters>[a-zA-Z]+)').match('123 abc')
827
987
 
828
988
  expect(md.named_captures(symbolize_names: true)).to eq(numbers: "123", letters: "abc")
829
989
  end
830
990
 
991
+ it "returns garbage-collectable symbol keys when symbolize_names: true" do
992
+ md = RE2::Regexp.new('(?P<collectable_named_capture>\d+)').match('123')
993
+ key = md.named_captures(symbolize_names: true).keys.first
994
+
995
+ expect(ObjectSpace.each_object(Symbol)).to include(key)
996
+ end
997
+
998
+ it "correctly encodes non-ASCII capturing group names as symbols" do
999
+ re = RE2::Regexp.new('(?P<café>\d+)', log_errors: false)
1000
+ skip "Underlying RE2 does not support non-ASCII capturing group names" unless re.ok?
1001
+
1002
+ expect(re.match('123').named_captures(symbolize_names: true)).to eq(café: "123")
1003
+ end
1004
+
831
1005
  it "returns string keys when symbolize_names: false" do
832
1006
  md = RE2::Regexp.new('(?P<numbers>\d+) (?P<letters>[a-zA-Z]+)').match('123 abc')
833
1007
 
@@ -858,10 +1032,10 @@ RSpec.describe RE2::MatchData do
858
1032
  expect(md.names.first.encoding).to eq(Encoding::UTF_8)
859
1033
  end
860
1034
 
861
- it "returns ISO-8859-1 strings if the pattern is not UTF-8" do
1035
+ it "returns UTF-8 strings if the pattern is not UTF-8" do
862
1036
  md = RE2::Regexp.new('(?P<numbers>\d+)', utf8: false).match('123')
863
1037
 
864
- expect(md.names.first.encoding).to eq(Encoding::ISO_8859_1)
1038
+ expect(md.names.first.encoding).to eq(Encoding::UTF_8)
865
1039
  end
866
1040
 
867
1041
  it "raises an error when called on an uninitialized object" do
@@ -876,6 +1050,22 @@ RSpec.describe RE2::MatchData do
876
1050
  expect(md.deconstruct_keys(nil)).to eq(numbers: '123', letters: 'abc')
877
1051
  end
878
1052
 
1053
+ it "returns garbage-collectable symbol keys if given nil" do
1054
+ md = RE2::Regexp.new('(?P<collectable_deconstructed_key>\d+)').match('123')
1055
+ key = md.deconstruct_keys(nil).keys.first
1056
+
1057
+ expect(ObjectSpace.each_object(Symbol)).to include(key)
1058
+ end
1059
+
1060
+ it "returns UTF-8 named captures for a Latin-1 pattern if given nil" do
1061
+ re = RE2::Regexp.new('(?P<café>\d+) (?P<letters>[a-zA-Z]+)'.encode("ISO-8859-1"), utf8: false, log_errors: false)
1062
+ skip "Underlying RE2 does not support non-ASCII capturing group names" unless re.ok?
1063
+
1064
+ md = re.match('123 abc')
1065
+
1066
+ expect(md.deconstruct_keys(nil)).to eq(café: '123', letters: 'abc')
1067
+ end
1068
+
879
1069
  it "returns only named captures if given names" do
880
1070
  md = RE2::Regexp.new('(?P<numbers>\d+) (?P<letters>[a-zA-Z]+)').match('123 abc')
881
1071
 
@@ -922,4 +1112,95 @@ RSpec.describe RE2::MatchData do
922
1112
  expect { described_class.allocate.deconstruct_keys(nil) }.to raise_error(TypeError, /uninitialized RE2::MatchData/)
923
1113
  end
924
1114
  end
1115
+
1116
+ describe "#==" do
1117
+ it "returns true for matches with the same regexp, text, and offsets" do
1118
+ re = RE2::Regexp.new('(\d+)')
1119
+
1120
+ expect(re.match("abc 123")).to eq(re.match("abc 123"))
1121
+ end
1122
+
1123
+ it "returns true for matches against equal but distinct regexps" do
1124
+ md = RE2::Regexp.new('(\d+)').match("abc 123")
1125
+
1126
+ expect(md).to eq(RE2::Regexp.new('(\d+)').match("abc 123"))
1127
+ end
1128
+
1129
+ it "returns true for matches against a copy of the text" do
1130
+ re = RE2::Regexp.new('(\d+)')
1131
+
1132
+ expect(re.match("abc 123")).to eq(re.match("abc 123".dup))
1133
+ end
1134
+
1135
+ it "returns true for matches with unmatched optional groups" do
1136
+ re = RE2::Regexp.new('a(b)?')
1137
+
1138
+ expect(re.match("ac")).to eq(re.match("ac"))
1139
+ end
1140
+
1141
+ it "returns false for matches against different text" do
1142
+ re = RE2::Regexp.new('(\d+)')
1143
+
1144
+ expect(re.match("abc 123")).not_to eq(re.match("abc 456"))
1145
+ end
1146
+
1147
+ it "returns false for the same captures at different offsets" do
1148
+ re = RE2::Regexp.new('(\d+)')
1149
+
1150
+ expect(re.match("123 abc")).not_to eq(re.match("abc 123"))
1151
+ end
1152
+
1153
+ it "returns false for matches against regexps with different options" do
1154
+ md = RE2::Regexp.new('(abc)').match("abc")
1155
+
1156
+ expect(md).not_to eq(RE2::Regexp.new('(abc)', case_sensitive: false).match("abc"))
1157
+ end
1158
+
1159
+ it "returns false for matches with a different number of submatches" do
1160
+ re = RE2::Regexp.new('(a)(b)')
1161
+
1162
+ expect(re.match("ab")).not_to eq(re.match("ab", submatches: 1))
1163
+ end
1164
+
1165
+ it "returns false for a non-MatchData" do
1166
+ expect(RE2::Regexp.new('(a)').match("a")).not_to eq("a")
1167
+ end
1168
+
1169
+ it "is aliased to eql?" do
1170
+ re = RE2::Regexp.new('(\d+)')
1171
+
1172
+ expect(re.match("abc 123")).to eql(re.match("abc 123"))
1173
+ end
1174
+
1175
+ it "raises an error when called on an uninitialized object" do
1176
+ expect { described_class.allocate == RE2::Regexp.new('(a)').match("a") }.to raise_error(TypeError, /uninitialized RE2::MatchData/)
1177
+ end
1178
+ end
1179
+
1180
+ describe "#hash" do
1181
+ it "is the same for equal matches" do
1182
+ re = RE2::Regexp.new('(\d+)')
1183
+
1184
+ expect(re.match("abc 123").hash).to eq(re.match("abc 123").hash)
1185
+ end
1186
+
1187
+ it "differs for matches at different offsets" do
1188
+ re = RE2::Regexp.new('(\d+)')
1189
+
1190
+ expect(re.match("123 abc").hash).not_to eq(re.match("abc 123").hash)
1191
+ end
1192
+
1193
+ it "allows matches to be used as Hash keys" do
1194
+ re = RE2::Regexp.new('(\d+)')
1195
+ hash = { re.match("abc 123") => :found }
1196
+
1197
+ expect(hash[re.match("abc 123")]).to eq(:found)
1198
+ end
1199
+
1200
+ it "allows matches to be deduplicated" do
1201
+ re = RE2::Regexp.new('(\d+)')
1202
+
1203
+ expect([re.match("abc 123"), re.match("abc 123")].uniq.size).to eq(1)
1204
+ end
1205
+ end
925
1206
  end