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/