diff --git a/string.c b/string.c
index 6faeb5d00e..fbc44086dc 100644
--- a/string.c
+++ b/string.c
@@ -11307,6 +11307,26 @@ rb_str_delete_suffix(VALUE str, VALUE suffix)
return rb_str_subseq(str, 0, RSTRING_LEN(str) - suffixlen);
}
+/*
+ * call-seq:
+ * ensure_suffix(suffix) -> new_string
+ *
+ * Returns a new string with the given suffix appended if not present,
+ * or a duplicate of self if it already ends with suffix.
+ *
+ * "script".ensure_suffix(".rb") # => "script.rb"
+ * "script.rb".ensure_suffix(".rb") # => "script.rb"
+ * "script.r".ensure_suffix(".rb") # => "script.r.rb"
+ *
+ */
+static VALUE
+rb_str_ensure_suffix(VALUE str, VALUE suffix)
+{
+ if (rb_str_end_with(1, &suffix, str)) return str_duplicate(rb_cString, str);
+
+ return rb_str_plus(str, suffix);
+}
+
void
rb_str_setter(VALUE val, ID id, VALUE *var)
{
@@ -12678,6 +12698,7 @@ Init_String(void)
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, "ensure_suffix", rb_str_ensure_suffix, 1);
rb_define_method(rb_cString, "sub!", rb_str_sub_bang, -1);
rb_define_method(rb_cString, "gsub!", rb_str_gsub_bang, -1);
diff --git a/test/ruby/test_string.rb b/test/ruby/test_string.rb
index d2099607fd..4974d0b27c 100644
--- a/test/ruby/test_string.rb
+++ b/test/ruby/test_string.rb
@@ -3737,6 +3737,18 @@ CODE
Warning[:deprecated] = deprecated
end
+ def test_string_ensure_suffix
+ s = S("foobar")
+
+ assert_equal("foobarbaz", s.ensure_suffix("baz"))
+ assert_equal("foobar", s.ensure_suffix("bar"))
+ assert_equal("foobarrb", s.ensure_suffix("rb"))
+ assert_equal(false, s.ensure_suffix("bar").equal?(s))
+ assert_equal("foobarBAR", s.ensure_suffix("BAR"))
+ assert_equal("foobar", s.ensure_suffix(""))
+ assert_equal(false, s.ensure_suffix("").equal?(s))
+ end
+
private
def assert_bytesplice_result(expected, s, *args)