diff options
author | Vladimir Dementyev <[email protected]> | 2023-04-18 19:19:31 -0400 |
---|---|---|
committer | GitHub <[email protected]> | 2023-04-19 11:19:31 +1200 |
commit | b09f5c7bf79d347aa6ed92964cff16d7da008ad9 (patch) | |
tree | 7166e834c07ed543bd9c1b86578748b718fddcf8 | |
parent | 2dff1d4fdabd0fafeeac675baaaf7b06bb3150f9 (diff) |
MatchData#named_captures: add optional symbolize_names keyword (#6952)
Notes
Notes:
Merged-By: ioquatix <[email protected]>
-rw-r--r-- | NEWS.md | 4 | ||||
-rw-r--r-- | re.c | 34 | ||||
-rw-r--r-- | spec/ruby/core/matchdata/named_captures_spec.rb | 12 | ||||
-rw-r--r-- | test/ruby/test_regexp.rb | 3 |
4 files changed, 49 insertions, 4 deletions
@@ -30,6 +30,10 @@ Note: We're only listing outstanding class updates. * `Dir#chdir` added for changing the directory to the directory specified by the provided `Dir` object. [[Feature #19347]] +* MatchData + + * MatchData#named_captures now accepts optional `symbolize_names` keyword. [[Feature #19591]] + * String * `String#unpack` now raises ArgumentError for unknown directives. [[Bug #19150]] @@ -2316,7 +2316,7 @@ match_named_captures_iter(const OnigUChar *name, const OnigUChar *name_end, /* * call-seq: - * named_captures -> hash + * named_captures(symbolize_names: false) -> hash * * Returns a hash of the named captures; * each key is a capture name; each value is its captured string or +nil+: @@ -2337,10 +2337,17 @@ match_named_captures_iter(const OnigUChar *name, const OnigUChar *name_end, * # => #<MatchData "01" a:"0" a:"1"> * m.named_captures #=> {"a" => "1"} * + * If keyword argument +symbolize_names+ is given + * a true value, the keys in the resulting hash are Symbols: + * + * m = /(?<a>.)(?<a>.)/.match("01") + * # => #<MatchData "01" a:"0" a:"1"> + * m.named_captures(symbolize_names: true) #=> {:a => "1"} + * */ static VALUE -match_named_captures(VALUE match) +match_named_captures(int argc, VALUE *argv, VALUE match) { VALUE hash; struct MEMO *memo; @@ -2349,8 +2356,27 @@ match_named_captures(VALUE match) if (NIL_P(RMATCH(match)->regexp)) return rb_hash_new(); + VALUE opt; + VALUE symbolize_names = 0; + + rb_scan_args(argc, argv, "0:", &opt); + + if (!NIL_P(opt)) { + static ID keyword_ids[1]; + + VALUE symbolize_names_val; + + if (!keyword_ids[0]) { + keyword_ids[0] = rb_intern_const("symbolize_names"); + } + rb_get_kwargs(opt, keyword_ids, 0, 1, &symbolize_names_val); + if (!UNDEF_P(symbolize_names_val) && RTEST(symbolize_names_val)) { + symbolize_names = 1; + } + } + hash = rb_hash_new(); - memo = MEMO_NEW(hash, match, 0); + memo = MEMO_NEW(hash, match, symbolize_names); onig_foreach_name(RREGEXP(RMATCH(match)->regexp)->ptr, match_named_captures_iter, (void*)memo); @@ -4754,7 +4780,7 @@ Init_Regexp(void) rb_define_method(rb_cMatch, "[]", match_aref, -1); rb_define_method(rb_cMatch, "captures", match_captures, 0); rb_define_alias(rb_cMatch, "deconstruct", "captures"); - rb_define_method(rb_cMatch, "named_captures", match_named_captures, 0); + rb_define_method(rb_cMatch, "named_captures", match_named_captures, -1); rb_define_method(rb_cMatch, "deconstruct_keys", match_deconstruct_keys, 1); rb_define_method(rb_cMatch, "values_at", match_values_at, -1); rb_define_method(rb_cMatch, "pre_match", rb_reg_match_pre, 0); diff --git a/spec/ruby/core/matchdata/named_captures_spec.rb b/spec/ruby/core/matchdata/named_captures_spec.rb index 9b1e324a24..5e4693d62d 100644 --- a/spec/ruby/core/matchdata/named_captures_spec.rb +++ b/spec/ruby/core/matchdata/named_captures_spec.rb @@ -12,4 +12,16 @@ describe 'MatchData#named_captures' do it 'returns the latest matched capture, even if a later one that does not match exists' do /\A(?<a>.)(?<b>.)(?<b>.)(?<a>.)?\z/.match('012').named_captures.should == { 'a' => '0', 'b' => '2' } end + + ruby_version_is "3.3" do + it 'returns a Hash with Symbol keys when symbolize_names is provided a true value' do + /(?<a>.)(?<b>.)?/.match('0').named_captures(symbolize_names: true).should == { a: '0', b: nil } + /(?<a>.)(?<b>.)?/.match('0').named_captures(symbolize_names: "truly").should == { a: '0', b: nil } + end + + it 'returns a Hash with String keys when symbolize_names is provided a false value' do + /(?<a>.)(?<b>.)?/.match('02').named_captures(symbolize_names: false).should == { 'a' => '0', 'b' => '2' } + /(?<a>.)(?<b>.)?/.match('02').named_captures(symbolize_names: nil).should == { 'a' => '0', 'b' => '2' } + end + end end diff --git a/test/ruby/test_regexp.rb b/test/ruby/test_regexp.rb index 6ec77a41a3..d50e481d2d 100644 --- a/test/ruby/test_regexp.rb +++ b/test/ruby/test_regexp.rb @@ -304,6 +304,9 @@ class TestRegexp < Test::Unit::TestCase assert_equal({'a' => '1', 'b' => '2', 'c' => '3'}, /^(?<a>.)(?<b>.)(?<c>.)?/.match('123').named_captures) assert_equal({'a' => '1', 'b' => '2', 'c' => ''}, /^(?<a>.)(?<b>.)(?<c>.?)/.match('12').named_captures) + assert_equal({a: '1', b: '2', c: ''}, /^(?<a>.)(?<b>.)(?<c>.?)/.match('12').named_captures(symbolize_names: true)) + assert_equal({'a' => '1', 'b' => '2', 'c' => ''}, /^(?<a>.)(?<b>.)(?<c>.?)/.match('12').named_captures(symbolize_names: false)) + assert_equal({'a' => 'x'}, /(?<a>x)|(?<a>y)/.match('x').named_captures) assert_equal({'a' => 'y'}, /(?<a>x)|(?<a>y)/.match('y').named_captures) |