[前][次][番号順一覧][スレッド一覧]

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/

[前][次][番号順一覧][スレッド一覧]