ruby-changes:47017
From: sonots <ko1@a...>
Date: Wed, 21 Jun 2017 16:43:31 +0900 (JST)
Subject: [ruby-changes:47017] sonots:r59132 (trunk): string.c: add String#delete_prefix and String#delete_prefix!
sonots 2017-06-21 16:43:26 +0900 (Wed, 21 Jun 2017) New Revision: 59132 https://svn.ruby-lang.org/cgi-bin/viewvc.cgi?view=revision&revision=59132 Log: string.c: add String#delete_prefix and String#delete_prefix! to remove leading substr [Feature #12694] [fix GH-1632] * string.c (rb_str_delete_prefix_bang): add a new method to remove prefix destuctively. * string.c (rb_str_delete_prefix): add a new method to remove prefix non-destuctively. * test/ruby/test_string.rb: add tests. Modified files: trunk/NEWS trunk/string.c trunk/test/ruby/test_string.rb Index: NEWS =================================================================== --- NEWS (revision 59131) +++ NEWS (revision 59132) @@ -85,6 +85,8 @@ with all sufficient information, see the https://github.com/ruby/ruby/blob/trunk/NEWS#L85 (same as "literal".freeze in Ruby 2.1+) [Feature #13295] * String#{casecmp,casecmp?} now return nil for non-string arguments instead of raising a TypeError. [Bug #13312] + * String##delete_prefix is added to remove prefix [Feature #12694] + * String#delete_prefix! is added to remove prefix destructively [Feature #12694] === Stdlib updates (outstanding ones only) Index: test/ruby/test_string.rb =================================================================== --- test/ruby/test_string.rb (revision 59131) +++ test/ruby/test_string.rb (revision 59132) @@ -2416,6 +2416,101 @@ CODE https://github.com/ruby/ruby/blob/trunk/test/ruby/test_string.rb#L2416 assert_equal("\u3042", s4) end + def test_delete_prefix + assert_raise(TypeError) { 'hello'.delete_prefix(nil) } + assert_raise(TypeError) { 'hello'.delete_prefix(1) } + assert_raise(TypeError) { 'hello'.delete_prefix(/hel/) } + + s = S("hello") + assert_equal("lo", s.delete_prefix('hel')) + assert_equal("hello", s) + + s = S("hello") + assert_equal("hello", s.delete_prefix('lo')) + assert_equal("hello", s) + + s = S("\u{3053 3093 306b 3061 306f}") + assert_equal("\u{306b 3061 306f}", s.delete_prefix("\u{3053 3093}")) + assert_equal("\u{3053 3093 306b 3061 306f}", s) + + s = S("\u{3053 3093 306b 3061 306f}") + assert_equal("\u{3053 3093 306b 3061 306f}", s.delete_prefix('hel')) + assert_equal("\u{3053 3093 306b 3061 306f}", s) + + s = S("hello") + assert_equal("hello", s.delete_prefix("\u{3053 3093}")) + assert_equal("hello", s) + + # skip if argument is a broken string + s = S("\xe3\x81\x82") + assert_equal("\xe3\x81\x82", s.delete_prefix("\xe3")) + assert_equal("\xe3\x81\x82", s) + + # clear coderange + s = S("\u{3053 3093}hello") + assert_not_predicate(s, :ascii_only?) + assert_predicate(s.delete_prefix("\u{3053 3093}"), :ascii_only?) + + # argument should be converted to String + klass = Class.new {|klass| def to_str; 'a'; end } + s = S("abba") + assert_equal("bba", s.delete_prefix(klass.new)) + assert_equal("abba", s) + end + + def test_delete_prefix_bang + assert_raise(TypeError) { 'hello'.delete_prefix!(nil) } + assert_raise(TypeError) { 'hello'.delete_prefix!(1) } + assert_raise(TypeError) { 'hello'.delete_prefix!(/hel/) } + + s = S("hello") + assert_equal("lo", s.delete_prefix!('hel')) + assert_equal("lo", s) + + s = S("hello") + assert_equal(nil, s.delete_prefix!('lo')) + assert_equal("hello", s) + + s = S("\u{3053 3093 306b 3061 306f}") + assert_equal("\u{306b 3061 306f}", s.delete_prefix!("\u{3053 3093}")) + assert_equal("\u{306b 3061 306f}", s) + + s = S("\u{3053 3093 306b 3061 306f}") + assert_equal(nil, s.delete_prefix!('hel')) + assert_equal("\u{3053 3093 306b 3061 306f}", s) + + s = S("hello") + assert_equal(nil, s.delete_prefix!("\u{3053 3093}")) + assert_equal("hello", s) + + # skip if argument is a broken string + s = S("\xe3\x81\x82") + assert_equal(nil, s.delete_prefix!("\xe3")) + assert_equal("\xe3\x81\x82", s) + + # clear coderange + s = S("\u{3053 3093}hello") + assert_not_predicate(s, :ascii_only?) + assert_predicate(s.delete_prefix!("\u{3053 3093}"), :ascii_only?) + + # argument should be converted to String + klass = Class.new {|klass| def to_str; 'a'; end } + s = S("abba") + assert_equal("bba", s.delete_prefix!(klass.new)) + assert_equal("bba", s) + + s = S("ax").freeze + assert_raise_with_message(RuntimeError, /frozen/) {s.delete_prefix!("a")} + + s = S("ax") + o = Struct.new(:s).new(s) + def o.to_str + s.freeze + "a" + end + assert_raise_with_message(RuntimeError, /frozen/) {s.delete_prefix!(o)} + end + =begin def test_symbol_table_overflow assert_in_out_err([], <<-INPUT, [], /symbol table overflow \(symbol [a-z]{8}\) \(RuntimeError\)/) Index: string.c =================================================================== --- string.c (revision 59131) +++ string.c (revision 59132) @@ -9191,6 +9191,72 @@ rb_str_end_with(int argc, VALUE *argv, V https://github.com/ruby/ruby/blob/trunk/string.c#L9191 return Qfalse; } +static long +deleted_prefix_length(VALUE str, VALUE prefix) +{ + char *strptr, *prefixptr; + long olen, prefixlen; + + StringValue(prefix); + if (is_broken_string(prefix)) return 0; + rb_enc_check(str, prefix); + + /* return 0 if not start with prefix */ + prefixlen = RSTRING_LEN(prefix); + if (prefixlen <= 0) return 0; + olen = RSTRING_LEN(str); + if (olen < prefixlen) return 0; + strptr = RSTRING_PTR(str); + prefixptr = RSTRING_PTR(prefix); + if (memcmp(strptr, prefixptr, prefixlen) != 0) return 0; + + return prefixlen; +} + +/* + * call-seq: + * str.delete_prefix!(prefix) -> self or nil + * + * Deletes leading <code>prefix</code> from <i>str</i>, returning + * <code>nil</code> if no change was made. + * + * "hello".delete_prefix!("hel") #=> "lo" + * "hello".delete_prefix!("llo") #=> nil + */ + +static VALUE +rb_str_delete_prefix_bang(VALUE str, VALUE prefix) +{ + long prefixlen; + str_modify_keep_cr(str); + + prefixlen = deleted_prefix_length(str, prefix); + if (prefixlen <= 0) return Qnil; + + return rb_str_drop_bytes(str, prefixlen); +} + +/* + * call-seq: + * str.delete_prefix(prefix) -> new_str + * + * Returns a copy of <i>str</i> with leading <code>prefix</code> deleted. + * + * "hello".delete_prefix("hel") #=> "lo" + * "hello".delete_prefix("llo") #=> "hello" + */ + +static VALUE +rb_str_delete_prefix(VALUE str, VALUE prefix) +{ + long prefixlen; + + prefixlen = deleted_prefix_length(str, prefix); + if (prefixlen <= 0) return rb_str_dup(str); + + return rb_str_subseq(str, prefixlen, RSTRING_LEN(str) - prefixlen); +} + void rb_str_setter(VALUE val, ID id, VALUE *var) { @@ -10346,6 +10412,7 @@ Init_String(void) https://github.com/ruby/ruby/blob/trunk/string.c#L10412 rb_define_method(rb_cString, "strip", rb_str_strip, 0); rb_define_method(rb_cString, "lstrip", rb_str_lstrip, 0); rb_define_method(rb_cString, "rstrip", rb_str_rstrip, 0); + rb_define_method(rb_cString, "delete_prefix", rb_str_delete_prefix, 1); rb_define_method(rb_cString, "sub!", rb_str_sub_bang, -1); rb_define_method(rb_cString, "gsub!", rb_str_gsub_bang, -1); @@ -10354,6 +10421,7 @@ Init_String(void) https://github.com/ruby/ruby/blob/trunk/string.c#L10421 rb_define_method(rb_cString, "strip!", rb_str_strip_bang, 0); rb_define_method(rb_cString, "lstrip!", rb_str_lstrip_bang, 0); rb_define_method(rb_cString, "rstrip!", rb_str_rstrip_bang, 0); + rb_define_method(rb_cString, "delete_prefix!", rb_str_delete_prefix_bang, 1); rb_define_method(rb_cString, "tr", rb_str_tr, 2); rb_define_method(rb_cString, "tr_s", rb_str_tr_s, 2); -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/