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. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@59377 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
parent
515ac69f95
commit
510957df33
79
string.c
79
string.c
@ -9250,6 +9250,83 @@ rb_str_delete_prefix(VALUE str, VALUE prefix)
|
|||||||
return rb_str_subseq(str, prefixlen, RSTRING_LEN(str) - prefixlen);
|
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
|
void
|
||||||
rb_str_setter(VALUE val, ID id, VALUE *var)
|
rb_str_setter(VALUE val, ID id, VALUE *var)
|
||||||
{
|
{
|
||||||
@ -10406,6 +10483,7 @@ Init_String(void)
|
|||||||
rb_define_method(rb_cString, "lstrip", rb_str_lstrip, 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, "rstrip", rb_str_rstrip, 0);
|
||||||
rb_define_method(rb_cString, "delete_prefix", rb_str_delete_prefix, 1);
|
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, "sub!", rb_str_sub_bang, -1);
|
||||||
rb_define_method(rb_cString, "gsub!", rb_str_gsub_bang, -1);
|
rb_define_method(rb_cString, "gsub!", rb_str_gsub_bang, -1);
|
||||||
@ -10415,6 +10493,7 @@ Init_String(void)
|
|||||||
rb_define_method(rb_cString, "lstrip!", rb_str_lstrip_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, "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_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", rb_str_tr, 2);
|
||||||
rb_define_method(rb_cString, "tr_s", rb_str_tr_s, 2);
|
rb_define_method(rb_cString, "tr_s", rb_str_tr_s, 2);
|
||||||
|
@ -409,6 +409,10 @@ CODE
|
|||||||
assert_equal("\xe3\x81\x82", s.chomp("\x82"))
|
assert_equal("\xe3\x81\x82", s.chomp("\x82"))
|
||||||
assert_equal("\xe3\x81\x82", s)
|
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
|
# clear coderange
|
||||||
s = S("hello\u{3053 3093}")
|
s = S("hello\u{3053 3093}")
|
||||||
assert_not_predicate(s, :ascii_only?)
|
assert_not_predicate(s, :ascii_only?)
|
||||||
@ -419,6 +423,14 @@ CODE
|
|||||||
s = S("abba")
|
s = S("abba")
|
||||||
assert_equal("abb", s.chomp(klass.new))
|
assert_equal("abb", s.chomp(klass.new))
|
||||||
assert_equal("abba", s)
|
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
|
ensure
|
||||||
$/ = save
|
$/ = save
|
||||||
end
|
end
|
||||||
@ -514,6 +526,10 @@ CODE
|
|||||||
assert_equal(nil, s.chomp!("\x82"))
|
assert_equal(nil, s.chomp!("\x82"))
|
||||||
assert_equal("\xe3\x81\x82", s)
|
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
|
# clear coderange
|
||||||
s = S("hello\u{3053 3093}")
|
s = S("hello\u{3053 3093}")
|
||||||
assert_not_predicate(s, :ascii_only?)
|
assert_not_predicate(s, :ascii_only?)
|
||||||
@ -524,6 +540,14 @@ CODE
|
|||||||
s = S("abba")
|
s = S("abba")
|
||||||
assert_equal("abb", s.chomp!(klass.new))
|
assert_equal("abb", s.chomp!(klass.new))
|
||||||
assert_equal("abb", s)
|
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
|
ensure
|
||||||
$/ = save
|
$/ = save
|
||||||
end
|
end
|
||||||
@ -2518,6 +2542,10 @@ CODE
|
|||||||
assert_equal("\xe3\x81\x82", s.delete_prefix("\xe3"))
|
assert_equal("\xe3\x81\x82", s.delete_prefix("\xe3"))
|
||||||
assert_equal("\xe3\x81\x82", s)
|
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
|
# clear coderange
|
||||||
s = S("\u{3053 3093}hello")
|
s = S("\u{3053 3093}hello")
|
||||||
assert_not_predicate(s, :ascii_only?)
|
assert_not_predicate(s, :ascii_only?)
|
||||||
@ -2583,6 +2611,123 @@ CODE
|
|||||||
assert_raise_with_message(RuntimeError, /frozen/) {s.delete_prefix!(o)}
|
assert_raise_with_message(RuntimeError, /frozen/) {s.delete_prefix!(o)}
|
||||||
end
|
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
|
=begin
|
||||||
def test_symbol_table_overflow
|
def test_symbol_table_overflow
|
||||||
assert_in_out_err([], <<-INPUT, [], /symbol table overflow \(symbol [a-z]{8}\) \(RuntimeError\)/)
|
assert_in_out_err([], <<-INPUT, [], /symbol table overflow \(symbol [a-z]{8}\) \(RuntimeError\)/)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user