re2 2.28.0-x86_64-linux-gnu → 2.29.0-x86_64-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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d149dfbab5aa104c93f829e99962f35922b6bdf123b6535ea95f925a052a9465
4
- data.tar.gz: dc26acf96cf4278134831e270bbdb0dbb93da8b840afc95f7416ccd56fe3281e
3
+ metadata.gz: c820ef6767846d99c2f7a45e0bbcfdfd5e4d87a135492a435d4c0c3abd04afe1
4
+ data.tar.gz: 41234ec0a65da25ea8ed64576945aa38e4a07c3c1e984f34909b66e2a30455eb
5
5
  SHA512:
6
- metadata.gz: 2249126b7e4af253c810aa8fbf9940f83ca5e37230935598073ed8b5da9fe53843615a8dec86ce3819b80c03f7a543fef54854780932e2bd10d1fc88dc3ab4cd
7
- data.tar.gz: 33440763d865e3d29b10e41bd0115b88a9d1d51c60f6e7c00b86606ca4fcc670ad04f387633376db22054c4b03beda07bea4294af6a5e4f662d5390ee338d579
6
+ metadata.gz: 1fdb0292918640afd131cf0363ec00b91268882f143037b012e7e65c2f9a6829f2a5c91ac21f91720b99d8175ea4e2e6e29cdcc6981300b9908d085bb11d23b8
7
+ data.tar.gz: 93fd57162da06b2820d509327cbd1b27b13c0cb21bb5d796a5c1982caa195fa86654a0dd4fadbf03138871fcb048f921b2b1b5cebd89f7affb53f61a5c5b28e3
data/README.md CHANGED
@@ -6,7 +6,7 @@ Python".
6
6
 
7
7
  [![Build Status](https://github.com/mudge/re2/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/mudge/re2/actions)
8
8
 
9
- **Current version:** 2.28.0
9
+ **Current version:** 2.29.0
10
10
  **Bundled RE2 version:** libre2.11 (2025-11-05)
11
11
 
12
12
  ```ruby
@@ -232,6 +232,14 @@ m.offset(1) #=> [4, 7]
232
232
  m.match_length(1) #=> 3
233
233
  ```
234
234
 
235
+ As RE2 works in bytes, the [`byteoffset`](https://mudge.name/re2/RE2/MatchData.html#byteoffset-instance_method), [`bytebegin`](https://mudge.name/re2/RE2/MatchData.html#bytebegin-instance_method) and [`byteend`](https://mudge.name/re2/RE2/MatchData.html#byteend-instance_method) of a match are cheaper to retrieve than their character equivalents:
236
+
237
+ ```ruby
238
+ m = RE2::Regexp.new('(Ruby)').partial_match("I ♥ Ruby")
239
+ m.offset(1) #=> [4, 8]
240
+ m.byteoffset(1) #=> [6, 10]
241
+ ```
242
+
235
243
  `RE2::MatchData` objects can also be used with Ruby's [pattern matching](https://docs.ruby-lang.org/en/3.2/syntax/pattern_matching_rdoc.html):
236
244
 
237
245
  ```ruby
@@ -483,6 +491,8 @@ Alternatively, you can set the `RE2_USE_SYSTEM_LIBRARIES` environment variable i
483
491
  when passing strings with null bytes.
484
492
  * Thanks to [Maciej Gajewski](https://github.com/konieczkow) for helping
485
493
  confirm issues with GC compaction and mutable strings.
494
+ * Thanks to [Konstantin Haase](https://github.com/rkh) for reporting that
495
+ `RE2::Regexp` shouldn't implement `to_str`.
486
496
 
487
497
  ## Contact
488
498
 
data/ext/re2/re2.cc CHANGED
@@ -713,11 +713,11 @@ static VALUE re2_matchdata_begin(const VALUE self, VALUE n) {
713
713
  re2::StringPiece *match = re2_matchdata_find_match(n, self);
714
714
  if (match == nullptr) {
715
715
  return Qnil;
716
- } else {
717
- long offset = match->data() - RSTRING_PTR(m->text);
718
-
719
- return LONG2NUM(rb_str_sublen(m->text, offset));
720
716
  }
717
+
718
+ long offset = match->data() - RSTRING_PTR(m->text);
719
+
720
+ return LONG2NUM(rb_str_sublen(m->text, offset));
721
721
  }
722
722
 
723
723
  /*
@@ -738,11 +738,11 @@ static VALUE re2_matchdata_end(const VALUE self, VALUE n) {
738
738
  re2::StringPiece *match = re2_matchdata_find_match(n, self);
739
739
  if (match == nullptr) {
740
740
  return Qnil;
741
- } else {
742
- long offset = (match->data() - RSTRING_PTR(m->text)) + match->size();
743
-
744
- return LONG2NUM(rb_str_sublen(m->text, offset));
745
741
  }
742
+
743
+ long offset = (match->data() - RSTRING_PTR(m->text)) + match->size();
744
+
745
+ return LONG2NUM(rb_str_sublen(m->text, offset));
746
746
  }
747
747
 
748
748
  /*
@@ -830,6 +830,85 @@ static VALUE re2_matchdata_offset(const VALUE self, VALUE n) {
830
830
  return array;
831
831
  }
832
832
 
833
+ /*
834
+ * Returns the byte offset of the start of the nth element of the
835
+ * {RE2::MatchData}, like Ruby's `MatchData#bytebegin`. This is cheaper than
836
+ * {RE2::MatchData#begin} as it does not need to count characters.
837
+ *
838
+ * @param [Integer, String, Symbol] n the name or number of the submatch
839
+ * @return [Integer, nil] the byte offset of the start of the match or `nil` if
840
+ * there is no such submatch
841
+ * @example
842
+ * m = RE2::Regexp.new('(Ruby)').partial_match("I ♥ Ruby")
843
+ * m.bytebegin(0) #=> 6
844
+ * m.begin(0) #=> 4
845
+ */
846
+ static VALUE re2_matchdata_bytebegin(const VALUE self, VALUE n) {
847
+ re2_matchdata *m = unwrap_re2_matchdata(self);
848
+
849
+ re2::StringPiece *match = re2_matchdata_find_match(n, self);
850
+ if (match == nullptr) {
851
+ return Qnil;
852
+ }
853
+
854
+ return LONG2NUM(match->data() - RSTRING_PTR(m->text));
855
+ }
856
+
857
+ /*
858
+ * Returns the byte offset of the byte following the end of the nth element of
859
+ * the {RE2::MatchData}, like Ruby's `MatchData#byteend`. This is cheaper than
860
+ * {RE2::MatchData#end} as it does not need to count characters.
861
+ *
862
+ * @param [Integer, String, Symbol] n the name or number of the match
863
+ * @return [Integer, nil] the byte offset of the byte following the end of the
864
+ * match or `nil` if there is no such match
865
+ * @example
866
+ * m = RE2::Regexp.new('(Ruby)').partial_match("I ♥ Ruby")
867
+ * m.byteend(0) #=> 10
868
+ * m.end(0) #=> 8
869
+ */
870
+ static VALUE re2_matchdata_byteend(const VALUE self, VALUE n) {
871
+ re2_matchdata *m = unwrap_re2_matchdata(self);
872
+
873
+ re2::StringPiece *match = re2_matchdata_find_match(n, self);
874
+ if (match == nullptr) {
875
+ return Qnil;
876
+ }
877
+
878
+ return LONG2NUM((match->data() - RSTRING_PTR(m->text)) + match->size());
879
+ }
880
+
881
+ /*
882
+ * Returns a two-element array containing the beginning and ending byte
883
+ * offsets of the nth match, like Ruby's `MatchData#byteoffset`. This is
884
+ * cheaper than {RE2::MatchData#offset} as it does not need to count
885
+ * characters.
886
+ *
887
+ * @param [Integer, String, Symbol] n the name or number of the match
888
+ * @return [Array<Integer>, nil] a two-element array with the beginning and
889
+ * ending byte offsets of the match or `nil` if there is no such match
890
+ * @example
891
+ * m = RE2::Regexp.new('(Ruby)').partial_match("I ♥ Ruby")
892
+ * m.byteoffset(0) #=> [6, 10]
893
+ * m.offset(0) #=> [4, 8]
894
+ */
895
+ static VALUE re2_matchdata_byteoffset(const VALUE self, VALUE n) {
896
+ re2_matchdata *m = unwrap_re2_matchdata(self);
897
+
898
+ re2::StringPiece *match = re2_matchdata_find_match(n, self);
899
+ if (match == nullptr) {
900
+ return Qnil;
901
+ }
902
+
903
+ long start = match->data() - RSTRING_PTR(m->text);
904
+
905
+ VALUE array = rb_ary_new2(2);
906
+ rb_ary_push(array, LONG2NUM(start));
907
+ rb_ary_push(array, LONG2NUM(start + match->size()));
908
+
909
+ return array;
910
+ }
911
+
833
912
  /*
834
913
  * Returns the length of the nth match in characters. This is equivalent to
835
914
  * `m[n].length` but without allocating a new string.
@@ -1804,7 +1883,7 @@ static VALUE re2_regexp_named_capturing_groups(const VALUE self) {
1804
1883
  * Returns either `true` or `false` indicating whether a successful match was
1805
1884
  * made if the pattern contains no capturing groups.
1806
1885
  *
1807
- * @param [String] text the text to search
1886
+ * @param [String, Symbol, nil] text the text to search; `nil` returns `nil`
1808
1887
  * @return [RE2::MatchData, nil] if the pattern contains capturing groups
1809
1888
  * @return [Boolean] if the pattern does not contain capturing groups
1810
1889
  * @raise [NoMemoryError] if there was not enough memory to allocate the submatches
@@ -1822,7 +1901,7 @@ static VALUE re2_regexp_named_capturing_groups(const VALUE self) {
1822
1901
  * specific number of submatches to extract (padded with `nil`s if
1823
1902
  * necessary).
1824
1903
  *
1825
- * @param [String] text the text to search
1904
+ * @param [String, Symbol, nil] text the text to search; `nil` returns `nil`
1826
1905
  * @param [Hash] options the options with which to perform the match
1827
1906
  * @option options [Integer] :startpos (0) offset at which to start matching
1828
1907
  * @option options [Integer] :endpos offset at which to stop matching, defaults to the text length
@@ -1855,7 +1934,7 @@ static VALUE re2_regexp_named_capturing_groups(const VALUE self) {
1855
1934
  * @deprecated Legacy syntax for matching against `text` with a specific
1856
1935
  * number of submatches to extract. Use `match(text, submatches: n)` instead.
1857
1936
  *
1858
- * @param [String] text the text to search
1937
+ * @param [String, Symbol, nil] text the text to search; `nil` returns `nil`
1859
1938
  * @param [Integer] submatches the number of submatches to extract
1860
1939
  * @return [RE2::MatchData, nil] if extracting any submatches
1861
1940
  * @return [Boolean] if not extracting any submatches
@@ -1874,6 +1953,13 @@ static VALUE re2_regexp_match(int argc, VALUE *argv, const VALUE self) {
1874
1953
 
1875
1954
  rb_scan_args(argc, argv, "11", &text, &options);
1876
1955
 
1956
+ if (NIL_P(text)) {
1957
+ return Qnil;
1958
+ }
1959
+ if (SYMBOL_P(text)) {
1960
+ text = rb_sym2str(text);
1961
+ }
1962
+
1877
1963
  /* Coerce and freeze text to prevent mutation. */
1878
1964
  StringValue(text);
1879
1965
  text = rb_str_new_frozen(text);
@@ -2020,11 +2106,17 @@ static VALUE re2_regexp_match(int argc, VALUE *argv, const VALUE self) {
2020
2106
  * {https://github.com/google/re2/blob/bc0faab533e2b27b85b8ad312abf061e33ed6b5d/re2/re2.h#L413-L427
2021
2107
  * `PartialMatch`}.
2022
2108
  *
2023
- * @param [String] text the text to search
2109
+ * @param [String, Symbol, nil] text the text to search; `nil` returns `false`
2024
2110
  * @return [Boolean] whether the match was successful
2025
2111
  * @raise [TypeError] if text cannot be coerced to a `String`
2026
2112
  */
2027
2113
  static VALUE re2_regexp_match_p(const VALUE self, VALUE text) {
2114
+ if (NIL_P(text)) {
2115
+ return Qfalse;
2116
+ }
2117
+ if (SYMBOL_P(text)) {
2118
+ text = rb_sym2str(text);
2119
+ }
2028
2120
  StringValue(text);
2029
2121
  text = rb_str_new_frozen(text);
2030
2122
 
@@ -2041,11 +2133,17 @@ static VALUE re2_regexp_match_p(const VALUE self, VALUE text) {
2041
2133
  * {https://github.com/google/re2/blob/bc0faab533e2b27b85b8ad312abf061e33ed6b5d/re2/re2.h#L376-L411
2042
2134
  * `FullMatch`}.
2043
2135
  *
2044
- * @param [String] text the text to search
2136
+ * @param [String, Symbol, nil] text the text to search; `nil` returns `false`
2045
2137
  * @return [Boolean] whether the match was successful
2046
2138
  * @raise [TypeError] if text cannot be coerced to a `String`
2047
2139
  */
2048
2140
  static VALUE re2_regexp_full_match_p(const VALUE self, VALUE text) {
2141
+ if (NIL_P(text)) {
2142
+ return Qfalse;
2143
+ }
2144
+ if (SYMBOL_P(text)) {
2145
+ text = rb_sym2str(text);
2146
+ }
2049
2147
  StringValue(text);
2050
2148
  text = rb_str_new_frozen(text);
2051
2149
 
@@ -2800,6 +2898,12 @@ extern "C" void Init_re2(void) {
2800
2898
  RUBY_METHOD_FUNC(re2_matchdata_post_match), 0);
2801
2899
  rb_define_method(re2_cMatchData, "offset",
2802
2900
  RUBY_METHOD_FUNC(re2_matchdata_offset), 1);
2901
+ rb_define_method(re2_cMatchData, "bytebegin",
2902
+ RUBY_METHOD_FUNC(re2_matchdata_bytebegin), 1);
2903
+ rb_define_method(re2_cMatchData, "byteend",
2904
+ RUBY_METHOD_FUNC(re2_matchdata_byteend), 1);
2905
+ rb_define_method(re2_cMatchData, "byteoffset",
2906
+ RUBY_METHOD_FUNC(re2_matchdata_byteoffset), 1);
2803
2907
  rb_define_method(re2_cMatchData, "match_length",
2804
2908
  RUBY_METHOD_FUNC(re2_matchdata_match_length), 1);
2805
2909
  rb_define_method(re2_cMatchData, "[]", RUBY_METHOD_FUNC(re2_matchdata_aref),
@@ -2866,14 +2970,11 @@ extern "C" void Init_re2(void) {
2866
2970
  rb_define_method(re2_cRegexp, "partial_match?",
2867
2971
  RUBY_METHOD_FUNC(re2_regexp_match_p), 1);
2868
2972
  rb_define_method(re2_cRegexp, "=~", RUBY_METHOD_FUNC(re2_regexp_match_p), 1);
2869
- rb_define_method(re2_cRegexp, "===", RUBY_METHOD_FUNC(re2_regexp_match_p), 1);
2870
2973
  rb_define_method(re2_cRegexp, "full_match?",
2871
2974
  RUBY_METHOD_FUNC(re2_regexp_full_match_p), 1);
2872
2975
  rb_define_method(re2_cRegexp, "scan",
2873
2976
  RUBY_METHOD_FUNC(re2_regexp_scan), 1);
2874
2977
  rb_define_method(re2_cRegexp, "to_s", RUBY_METHOD_FUNC(re2_regexp_to_s), 0);
2875
- rb_define_method(re2_cRegexp, "to_str", RUBY_METHOD_FUNC(re2_regexp_to_s),
2876
- 0);
2877
2978
  rb_define_method(re2_cRegexp, "pattern", RUBY_METHOD_FUNC(re2_regexp_to_s),
2878
2979
  0);
2879
2980
  rb_define_method(re2_cRegexp, "source", RUBY_METHOD_FUNC(re2_regexp_to_s),
data/lib/3.1/re2.so CHANGED
Binary file
data/lib/3.2/re2.so CHANGED
Binary file
data/lib/3.3/re2.so CHANGED
Binary file
data/lib/3.4/re2.so CHANGED
Binary file
data/lib/4.0/re2.so CHANGED
Binary file
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ # re2 (https://github.com/mudge/re2)
4
+ # Ruby bindings to RE2, a "fast, safe, thread-friendly alternative to
5
+ # backtracking regular expression engines like those used in PCRE, Perl, and
6
+ # Python".
7
+ #
8
+ # Copyright (c) 2010, Paul Mucur (https://mudge.name)
9
+ # Released under the BSD Licence, please see LICENSE.txt
10
+
11
+
12
+ module RE2
13
+ class MatchData
14
+ # Return whether this match has the same regular expression, text, and
15
+ # submatch offsets as another {RE2::MatchData}, like Ruby's own
16
+ # `MatchData#==`.
17
+ #
18
+ # @param [Object] other the object to compare against
19
+ # @return [Boolean] whether the two matches are equal
20
+ # @example
21
+ # re = RE2::Regexp.new('(\d+)')
22
+ # re.match("abc 123") == re.match("abc 123") #=> true
23
+ # re.match("abc 123") == re.match("123 abc") #=> false
24
+ def ==(other)
25
+ other.is_a?(RE2::MatchData) &&
26
+ regexp == other.regexp &&
27
+ string == other.string &&
28
+ byteoffsets == other.byteoffsets
29
+ end
30
+
31
+ alias_method :eql?, :==
32
+
33
+ # Return a hash based on the regular expression, text, and submatch offsets
34
+ # so that equal {RE2::MatchData}s can be used as Hash keys.
35
+ #
36
+ # @return [Integer] the hash of the match
37
+ def hash
38
+ [regexp, string, byteoffsets].hash
39
+ end
40
+
41
+ protected
42
+
43
+ def byteoffsets
44
+ Array.new(size) { |i| byteoffset(i) }
45
+ end
46
+ end
47
+ end
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.28.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",
@@ -632,6 +632,108 @@ RSpec.describe RE2::MatchData do
632
632
  end
633
633
  end
634
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
+
635
737
  describe "#offset" do
636
738
  it "returns the offset of a match by index" do
637
739
  md = RE2::Regexp.new('ob (\d+)').match("bob 123")
@@ -1010,4 +1112,95 @@ RSpec.describe RE2::MatchData do
1010
1112
  expect { described_class.allocate.deconstruct_keys(nil) }.to raise_error(TypeError, /uninitialized RE2::MatchData/)
1011
1113
  end
1012
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
1013
1206
  end
@@ -241,24 +241,6 @@ RSpec.describe RE2::Regexp do
241
241
  end
242
242
  end
243
243
 
244
- describe "#to_str" do
245
- it "returns the original pattern" do
246
- string = RE2::Regexp.new('w(o)(o)').to_str
247
-
248
- expect(string).to eq("w(o)(o)")
249
- end
250
-
251
- it "returns the pattern even if invalid" do
252
- string = RE2::Regexp.new('???', log_errors: false).to_str
253
-
254
- expect(string).to eq("???")
255
- end
256
-
257
- it "raises an error when called on an uninitialized object" do
258
- expect { described_class.allocate.to_str }.to raise_error(TypeError, /uninitialized RE2::Regexp/)
259
- end
260
- end
261
-
262
244
  describe "#pattern" do
263
245
  it "returns the original pattern" do
264
246
  pattern = RE2::Regexp.new('w(o)(o)').pattern
@@ -515,6 +497,18 @@ RSpec.describe RE2::Regexp do
515
497
  expect(re.match("My name is Alice Bloggs")).to eq(true)
516
498
  end
517
499
 
500
+ it "returns nil for nil" do
501
+ re = RE2::Regexp.new('.*')
502
+
503
+ expect(re.match(nil)).to be_nil
504
+ end
505
+
506
+ it "matches against symbols" do
507
+ re = RE2::Regexp.new('(\d+)')
508
+
509
+ expect(re.match(:abc123)).to be_a(RE2::MatchData)
510
+ end
511
+
518
512
  it "supports matching against text containing null bytes" do
519
513
  re = RE2::Regexp.new("a\0b")
520
514
 
@@ -536,7 +530,7 @@ RSpec.describe RE2::Regexp do
536
530
  it "raises an exception when given text that cannot be coerced to a string" do
537
531
  re = RE2::Regexp.new('My name is (\w+) (\w+)')
538
532
 
539
- expect { re.match(nil) }.to raise_error(TypeError)
533
+ expect { re.match(0) }.to raise_error(TypeError)
540
534
  end
541
535
 
542
536
  it "returns nil with an invalid pattern" do
@@ -826,6 +820,18 @@ RSpec.describe RE2::Regexp do
826
820
  expect(re.match?("My name is Alice Bloggs")).to eq(false)
827
821
  end
828
822
 
823
+ it "returns false for nil" do
824
+ re = RE2::Regexp.new('.*')
825
+
826
+ expect(re.match?(nil)).to eq(false)
827
+ end
828
+
829
+ it "matches against symbols" do
830
+ re = RE2::Regexp.new('\d+')
831
+
832
+ expect(re.match?(:abc123)).to eq(true)
833
+ end
834
+
829
835
  it "raises an exception if text cannot be coerced to a string" do
830
836
  re = RE2::Regexp.new('My name is (\S+) (\S+)')
831
837
 
@@ -868,6 +874,18 @@ RSpec.describe RE2::Regexp do
868
874
  expect(re.partial_match?("My name is Alice Bloggs")).to eq(false)
869
875
  end
870
876
 
877
+ it "returns false for nil" do
878
+ re = RE2::Regexp.new('.*')
879
+
880
+ expect(re.partial_match?(nil)).to eq(false)
881
+ end
882
+
883
+ it "matches against symbols" do
884
+ re = RE2::Regexp.new('\d+')
885
+
886
+ expect(re.partial_match?(:abc123)).to eq(true)
887
+ end
888
+
871
889
  it "raises an exception if text cannot be coerced to a string" do
872
890
  re = RE2::Regexp.new('My name is (\S+) (\S+)')
873
891
 
@@ -910,6 +928,18 @@ RSpec.describe RE2::Regexp do
910
928
  expect(re =~ "My name is Alice Bloggs").to eq(false)
911
929
  end
912
930
 
931
+ it "returns false for nil" do
932
+ re = RE2::Regexp.new('.*')
933
+
934
+ expect(re =~ nil).to eq(false)
935
+ end
936
+
937
+ it "matches against symbols" do
938
+ re = RE2::Regexp.new('\d+')
939
+
940
+ expect(re =~ :abc123).to eq(true)
941
+ end
942
+
913
943
  it "raises an exception if text cannot be coerced to a string" do
914
944
  re = RE2::Regexp.new('My name is (\S+) (\S+)')
915
945
 
@@ -921,6 +951,52 @@ RSpec.describe RE2::Regexp do
921
951
  end
922
952
  end
923
953
 
954
+ describe "#==" do
955
+ it "returns true for regexps with the same pattern and options" do
956
+ expect(RE2::Regexp.new('a(b)', case_sensitive: false)).to eq(RE2::Regexp.new('a(b)', case_sensitive: false))
957
+ end
958
+
959
+ it "returns false for regexps with different patterns" do
960
+ expect(RE2::Regexp.new('a(b)')).not_to eq(RE2::Regexp.new('a(c)'))
961
+ end
962
+
963
+ it "returns false for regexps with different options" do
964
+ expect(RE2::Regexp.new('a(b)')).not_to eq(RE2::Regexp.new('a(b)', case_sensitive: false))
965
+ end
966
+
967
+ it "returns false for a String with the same pattern" do
968
+ expect(RE2::Regexp.new('a(b)')).not_to eq('a(b)')
969
+ end
970
+
971
+ it "is aliased to eql?" do
972
+ expect(RE2::Regexp.new('a(b)')).to eql(RE2::Regexp.new('a(b)'))
973
+ end
974
+
975
+ it "raises an error when called on an uninitialized object" do
976
+ expect { described_class.allocate == RE2::Regexp.new('a') }.to raise_error(TypeError, /uninitialized RE2::Regexp/)
977
+ end
978
+ end
979
+
980
+ describe "#hash" do
981
+ it "is the same for equal regexps" do
982
+ expect(RE2::Regexp.new('a(b)').hash).to eq(RE2::Regexp.new('a(b)').hash)
983
+ end
984
+
985
+ it "differs for regexps with different options" do
986
+ expect(RE2::Regexp.new('a(b)').hash).not_to eq(RE2::Regexp.new('a(b)', case_sensitive: false).hash)
987
+ end
988
+
989
+ it "allows regexps to be used as Hash keys" do
990
+ hash = { RE2::Regexp.new('a(b)') => :found }
991
+
992
+ expect(hash[RE2::Regexp.new('a(b)')]).to eq(:found)
993
+ end
994
+
995
+ it "allows regexps to be deduplicated" do
996
+ expect([RE2::Regexp.new('a'), RE2::Regexp.new('a')].uniq.size).to eq(1)
997
+ end
998
+ end
999
+
924
1000
  describe "#===" do
925
1001
  it "returns only true or false even if there are capturing groups", :aggregate_failures do
926
1002
  re = RE2::Regexp.new('My name is (\S+) (\S+)')
@@ -935,10 +1011,31 @@ RSpec.describe RE2::Regexp do
935
1011
  expect(re === "My name is Alice Bloggs").to eq(false)
936
1012
  end
937
1013
 
938
- it "raises an exception if text cannot be coerced to a string" do
1014
+ it "matches against symbols" do
1015
+ re = RE2::Regexp.new('\d+')
1016
+
1017
+ expect(re === :abc123).to eq(true)
1018
+ end
1019
+
1020
+ it "returns false if text cannot be coerced to a string" do
939
1021
  re = RE2::Regexp.new('My name is (\S+) (\S+)')
940
1022
 
941
- expect { re === 0 }.to raise_error(TypeError)
1023
+ expect(re === 0).to eq(false)
1024
+ end
1025
+
1026
+ it "returns false for nil" do
1027
+ re = RE2::Regexp.new('.*')
1028
+
1029
+ expect(re === nil).to eq(false)
1030
+ end
1031
+
1032
+ it "can be used in a case statement" do
1033
+ result = case 123
1034
+ when RE2::Regexp.new('\d+') then :matched
1035
+ else :not_matched
1036
+ end
1037
+
1038
+ expect(result).to eq(:not_matched)
942
1039
  end
943
1040
 
944
1041
  it "raises an error when called on an uninitialized object" do
@@ -967,6 +1064,18 @@ RSpec.describe RE2::Regexp do
967
1064
  expect(re.full_match?("My name is Alice Bloggs")).to eq(false)
968
1065
  end
969
1066
 
1067
+ it "returns false for nil" do
1068
+ re = RE2::Regexp.new('.*')
1069
+
1070
+ expect(re.full_match?(nil)).to eq(false)
1071
+ end
1072
+
1073
+ it "matches against symbols" do
1074
+ re = RE2::Regexp.new('\w+')
1075
+
1076
+ expect(re.full_match?(:abc123)).to eq(true)
1077
+ end
1078
+
970
1079
  it "raises an exception if text cannot be coerced to a string" do
971
1080
  re = RE2::Regexp.new('My name is (\S+) (\S+)')
972
1081
 
@@ -1159,6 +1268,18 @@ RSpec.describe RE2::Regexp do
1159
1268
  expect(r.partial_match("foo bar")).to eq(true)
1160
1269
  end
1161
1270
 
1271
+ it "returns nil for nil" do
1272
+ re = RE2::Regexp.new('.*')
1273
+
1274
+ expect(re.partial_match(nil)).to be_nil
1275
+ end
1276
+
1277
+ it "matches against symbols" do
1278
+ re = RE2::Regexp.new('(\d+)')
1279
+
1280
+ expect(re.partial_match(:abc123)).to be_a(RE2::MatchData)
1281
+ end
1282
+
1162
1283
  it "can set the number of submatches to extract", :aggregate_failures do
1163
1284
  r = RE2::Regexp.new('f(o+)(a+)')
1164
1285
  m = r.partial_match("fooaa bar", submatches: 1)
@@ -1219,6 +1340,18 @@ RSpec.describe RE2::Regexp do
1219
1340
  expect(r.full_match("foo")).to eq(true)
1220
1341
  end
1221
1342
 
1343
+ it "returns nil for nil" do
1344
+ re = RE2::Regexp.new('.*')
1345
+
1346
+ expect(re.full_match(nil)).to be_nil
1347
+ end
1348
+
1349
+ it "matches against symbols" do
1350
+ re = RE2::Regexp.new('(\w+)')
1351
+
1352
+ expect(re.full_match(:abc123)).to be_a(RE2::MatchData)
1353
+ end
1354
+
1222
1355
  it "can set the number of submatches to extract", :aggregate_failures do
1223
1356
  r = RE2::Regexp.new('f(o+)(a+)')
1224
1357
  m = r.full_match("fooaa", submatches: 1)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: re2
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.28.0
4
+ version: 2.29.0
5
5
  platform: x86_64-linux-gnu
6
6
  authors:
7
7
  - Paul Mucur
@@ -74,6 +74,7 @@ files:
74
74
  - lib/3.4/re2.so
75
75
  - lib/4.0/re2.so
76
76
  - lib/re2.rb
77
+ - lib/re2/match_data.rb
77
78
  - lib/re2/regexp.rb
78
79
  - lib/re2/scanner.rb
79
80
  - lib/re2/string.rb