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

ruby-changes:47262

From: sonots <ko1@a...>
Date: Fri, 21 Jul 2017 01:29:27 +0900 (JST)
Subject: [ruby-changes:47262] sonots:r59377 (trunk): string.c: add String#delete_suffix and String#delete_suffix!

sonots	2017-07-21 01:29:19 +0900 (Fri, 21 Jul 2017)

  New Revision: 59377

  https://svn.ruby-lang.org/cgi-bin/viewvc.cgi?view=revision&revision=59377

  Log:
    string.c: add String#delete_suffix and String#delete_suffix!
    to remove trailing suffix [Feature #13665] [Fix GH-1661]
    
    * string.c (rb_str_delete_suffix_bang): add a new method
      to remove suffix destuctively.
    
    * string.c (rb_str_delete_suffix): add a new method
      to remove suffix non-destuctively.
    
    * test/ruby/test_string.rb: add tests.

  Modified files:
    trunk/string.c
    trunk/test/ruby/test_string.rb
Index: string.c
===================================================================
--- string.c	(revision 59376)
+++ string.c	(revision 59377)
@@ -9250,6 +9250,83 @@ rb_str_delete_prefix(VALUE str, VALUE pr https://github.com/ruby/ruby/blob/trunk/string.c#L9250
     return rb_str_subseq(str, prefixlen, RSTRING_LEN(str) - prefixlen);
 }
 
+static long
+deleted_suffix_length(VALUE str, VALUE suffix)
+{
+    char *strptr, *suffixptr, *s;
+    long olen, suffixlen;
+    rb_encoding *enc;
+
+    StringValue(suffix);
+    if (is_broken_string(suffix)) return 0;
+    enc = rb_enc_check(str, suffix);
+
+    /* return 0 if not start with suffix */
+    suffixlen = RSTRING_LEN(suffix);
+    if (suffixlen <= 0) return 0;
+    olen = RSTRING_LEN(str);
+    if (olen < suffixlen) return 0;
+    strptr = RSTRING_PTR(str);
+    suffixptr = RSTRING_PTR(suffix);
+    s = strptr + olen - suffixlen;
+    if (memcmp(s, suffixptr, suffixlen) != 0) return 0;
+    if (rb_enc_left_char_head(strptr, s, strptr + olen, enc) != s) return 0;
+
+    return suffixlen;
+}
+
+/*
+ *  call-seq:
+ *     str.delete_suffix!(suffix) -> self or nil
+ *
+ *  Deletes trailing <code>suffix</code> from <i>str</i>, returning
+ *  <code>nil</code> if no change was made.
+ *
+ *     "hello".delete_suffix!("llo") #=> "he"
+ *     "hello".delete_suffix!("hel") #=> nil
+ */
+
+static VALUE
+rb_str_delete_suffix_bang(VALUE str, VALUE suffix)
+{
+    long olen, suffixlen, len;
+    str_modifiable(str);
+
+    suffixlen = deleted_suffix_length(str, suffix);
+    if (suffixlen <= 0) return Qnil;
+
+    olen = RSTRING_LEN(str);
+    str_modify_keep_cr(str);
+    len = olen - suffixlen;
+    STR_SET_LEN(str, len);
+    TERM_FILL(&RSTRING_PTR(str)[len], TERM_LEN(str));
+    if (ENC_CODERANGE(str) != ENC_CODERANGE_7BIT) {
+	ENC_CODERANGE_CLEAR(str);
+    }
+    return str;
+}
+
+/*
+ *  call-seq:
+ *     str.delete_suffix(suffix) -> new_str
+ *
+ *  Returns a copy of <i>str</i> with trailing <code>suffix</code> deleted.
+ *
+ *     "hello".delete_suffix("llo") #=> "he"
+ *     "hello".delete_suffix("hel") #=> "hello"
+ */
+
+static VALUE
+rb_str_delete_suffix(VALUE str, VALUE suffix)
+{
+    long suffixlen;
+
+    suffixlen = deleted_suffix_length(str, suffix);
+    if (suffixlen <= 0) return rb_str_dup(str);
+
+    return rb_str_subseq(str, 0, RSTRING_LEN(str) - suffixlen);
+}
+
 void
 rb_str_setter(VALUE val, ID id, VALUE *var)
 {
@@ -10406,6 +10483,7 @@ Init_String(void) https://github.com/ruby/ruby/blob/trunk/string.c#L10483
     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, "delete_suffix", rb_str_delete_suffix, 1);
 
     rb_define_method(rb_cString, "sub!", rb_str_sub_bang, -1);
     rb_define_method(rb_cString, "gsub!", rb_str_gsub_bang, -1);
@@ -10415,6 +10493,7 @@ Init_String(void) https://github.com/ruby/ruby/blob/trunk/string.c#L10493
     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, "delete_suffix!", rb_str_delete_suffix_bang, 1);
 
     rb_define_method(rb_cString, "tr", rb_str_tr, 2);
     rb_define_method(rb_cString, "tr_s", rb_str_tr_s, 2);
Index: test/ruby/test_string.rb
===================================================================
--- test/ruby/test_string.rb	(revision 59376)
+++ test/ruby/test_string.rb	(revision 59377)
@@ -409,6 +409,10 @@ CODE https://github.com/ruby/ruby/blob/trunk/test/ruby/test_string.rb#L409
     assert_equal("\xe3\x81\x82", s.chomp("\x82"))
     assert_equal("\xe3\x81\x82", s)
 
+    s = S("\x95\x5c").force_encoding("Shift_JIS")
+    assert_equal("\x95\x5c".force_encoding("Shift_JIS"), s.chomp("\x5c"))
+    assert_equal("\x95\x5c".force_encoding("Shift_JIS"), s)
+
     # clear coderange
     s = S("hello\u{3053 3093}")
     assert_not_predicate(s, :ascii_only?)
@@ -419,6 +423,14 @@ CODE https://github.com/ruby/ruby/blob/trunk/test/ruby/test_string.rb#L423
     s = S("abba")
     assert_equal("abb", s.chomp(klass.new))
     assert_equal("abba", s)
+
+    # chomp removes any of "\n", "\r\n", "\r" when "\n" is specified
+    s = "foo\n"
+    assert_equal("foo", s.chomp("\n"))
+    s = "foo\r\n"
+    assert_equal("foo", s.chomp("\n"))
+    s = "foo\r"
+    assert_equal("foo", s.chomp("\n"))
   ensure
     $/ = save
   end
@@ -514,6 +526,10 @@ CODE https://github.com/ruby/ruby/blob/trunk/test/ruby/test_string.rb#L526
     assert_equal(nil, s.chomp!("\x82"))
     assert_equal("\xe3\x81\x82", s)
 
+    s = S("\x95\x5c").force_encoding("Shift_JIS")
+    assert_equal(nil, s.chomp!("\x5c"))
+    assert_equal("\x95\x5c".force_encoding("Shift_JIS"), s)
+
     # clear coderange
     s = S("hello\u{3053 3093}")
     assert_not_predicate(s, :ascii_only?)
@@ -524,6 +540,14 @@ CODE https://github.com/ruby/ruby/blob/trunk/test/ruby/test_string.rb#L540
     s = S("abba")
     assert_equal("abb", s.chomp!(klass.new))
     assert_equal("abb", s)
+
+    # chomp removes any of "\n", "\r\n", "\r" when "\n" is specified
+    s = "foo\n"
+    assert_equal("foo", s.chomp!("\n"))
+    s = "foo\r\n"
+    assert_equal("foo", s.chomp!("\n"))
+    s = "foo\r"
+    assert_equal("foo", s.chomp!("\n"))
   ensure
     $/ = save
   end
@@ -2518,6 +2542,10 @@ CODE https://github.com/ruby/ruby/blob/trunk/test/ruby/test_string.rb#L2542
     assert_equal("\xe3\x81\x82", s.delete_prefix("\xe3"))
     assert_equal("\xe3\x81\x82", s)
 
+    s = S("\x95\x5c").force_encoding("Shift_JIS")
+    assert_equal("\x95\x5c".force_encoding("Shift_JIS"), s.delete_prefix("\x95"))
+    assert_equal("\x95\x5c".force_encoding("Shift_JIS"), s)
+
     # clear coderange
     s = S("\u{3053 3093}hello")
     assert_not_predicate(s, :ascii_only?)
@@ -2583,6 +2611,123 @@ CODE https://github.com/ruby/ruby/blob/trunk/test/ruby/test_string.rb#L2611
     assert_raise_with_message(RuntimeError, /frozen/) {s.delete_prefix!(o)}
   end
 
+  def test_delete_suffix
+    assert_raise(TypeError) { 'hello'.delete_suffix(nil) }
+    assert_raise(TypeError) { 'hello'.delete_suffix(1) }
+    assert_raise(TypeError) { 'hello'.delete_suffix(/hel/) }
+
+    s = S("hello")
+    assert_equal("hel", s.delete_suffix('lo'))
+    assert_equal("hello", s)
+
+    s = S("hello")
+    assert_equal("hello", s.delete_suffix('he'))
+    assert_equal("hello", s)
+
+    s = S("\u{3053 3093 306b 3061 306f}")
+    assert_equal("\u{3053 3093 306b}", s.delete_suffix("\u{3061 306f}"))
+    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_suffix('lo'))
+    assert_equal("\u{3053 3093 306b 3061 306f}", s)
+
+    s = S("hello")
+    assert_equal("hello", s.delete_suffix("\u{3061 306f}"))
+    assert_equal("hello", s)
+
+    # skip if argument is a broken string
+    s = S("\xe3\x81\x82")
+    assert_equal("\xe3\x81\x82", s.delete_suffix("\x82"))
+    assert_equal("\xe3\x81\x82", s)
+
+    # clear coderange
+    s = S("hello\u{3053 3093}")
+    assert_not_predicate(s, :ascii_only?)
+    assert_predicate(s.delete_suffix("\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("abb", s.delete_suffix(klass.new))
+    assert_equal("abba", s)
+
+    # chomp removes any of "\n", "\r\n", "\r" when "\n" is specified,
+    # but delete_suffix does not
+    s = "foo\n"
+    assert_equal("foo", s.delete_suffix("\n"))
+    s = "foo\r\n"
+    assert_equal("foo\r", s.delete_suffix("\n"))
+    s = "foo\r"
+    assert_equal("foo\r", s.delete_suffix("\n"))
+  end
+
+  def test_delete_suffix_bang
+    assert_raise(TypeError) { 'hello'.delete_suffix!(nil) }
+    assert_raise(TypeError) { 'hello'.delete_suffix!(1) }
+    assert_raise(TypeError) { 'hello'.delete_suffix!(/hel/) }
+
+    s = S("hello").freeze
+    assert_raise_with_message(RuntimeError, /frozen/) {s.delete_suffix!('lo')}
+
+    s = S("ax")
+    o = Struct.new(:s).new(s)
+    def o.to_str
+      s.freeze
+      "x"
+    end
+    assert_raise_with_message(RuntimeError, /frozen/) {s.delete_suffix!(o)}
+
+    s = S("hello")
+    assert_equal("hel", s.delete_suffix!('lo'))
+    assert_equal("hel", s)
+
+    s = S("hello")
+    assert_equal(nil, s.delete_suffix!('he'))
+    assert_equal("hello", s)
+
+    s = S("\u{3053 3093 306b 3061 306f}")
+    assert_equal("\u{3053 3093 306b}", s.delete_suffix!("\u{3061 306f}"))
+    assert_equal("\u{3053 3093 306b}", s)
+
+    s = S("\u{3053 3093 306b 3061 306f}")
+    assert_equal(nil, s.delete_suffix!('lo'))
+    assert_equal("\u{3053 3093 306b 3061 306f}", s)
+
+    s = S("hello")
+    assert_equal(nil, s.delete_suffix!("\u{3061 306f}"))
+    assert_equal("hello", s)
+
+    # skip if argument is a broken string
+    s = S("\xe3\x81\x82")
+    assert_equal(nil, s.delete_suffix!("\x82"))
+    assert_equal("\xe3\x81\x82", s)
+
+    s = S("\x95\x5c").force_encoding("Shift_JIS")
+    assert_equal(nil, s.delete_suffix!("\x5c"))
+    assert_equal("\x95\x5c".force_encoding("Shift_JIS"), s)
+
+    # clear coderange
+    s = S("hello\u{3053 3093}")
+    assert_not_predicate(s, :ascii_only?)
+    assert_predicate(s.delete_suffix!("\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("abb", s.delete_suffix!(klass.new))
+    assert_equal("abb", s)
+
+    # chomp removes any of "\n", "\r\n", "\r" when "\n" is specified,
+    # but delete_suffix does not
+    s = "foo\n"
+    assert_equal("foo", s.delete_suffix!("\n"))
+    s = "foo\r\n"
+    assert_equal("foo\r", s.delete_suffix!("\n"))
+    s = "foo\r"
+    assert_equal(nil, s.delete_suffix!("\n"))
+  end
+
 =begin
   def test_symbol_table_overflow
     assert_in_out_err([], <<-INPUT, [], /symbol table overflow \(symbol [a-z]{8}\) \(RuntimeError\)/)

--
ML: ruby-changes@q...
Info: http://www.atdot.net/~ko1/quickml/

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