Merge pull request #6580 from dearblue/File.basename

Improve `File.basename`
This commit is contained in:
Yukihiro "Matz" Matsumoto
2026-05-08 11:41:36 +09:00
committed by GitHub
2 changed files with 62 additions and 55 deletions
+28 -55
View File
@@ -280,85 +280,58 @@ mrb_file_dirname(mrb_state *mrb, mrb_value klass)
static mrb_value
mrb_file_basename(mrb_state *mrb, mrb_value klass)
{
#if defined(_WIN32)
char bname[_MAX_DIR];
char extname[_MAX_EXT];
char *path;
const char *suffix = NULL;
mrb_get_args(mrb, "z|z", &path, &suffix);
size_t ridx = strlen(path);
if (ridx > 0) {
ridx--;
while (ridx > 0 && (path[ridx] == '/' || path[ridx] == '\\')) {
path[ridx] = '\0';
ridx--;
}
if (ridx == 0 && path[0] == '/') {
mrb_value result = mrb_str_new_cstr(mrb, path);
if (suffix && *suffix) {
mrb_int blen = RSTRING_LEN(result);
mrb_int slen = strlen(suffix);
if (blen > slen && memcmp(RSTRING_PTR(result) + blen - slen, suffix, slen) == 0) {
mrb_str_resize(mrb, result, blen - slen);
}
}
return result;
}
}
_splitpath((const char*)path, NULL, NULL, bname, extname);
mrb_value buffer = mrb_str_new_cstr(mrb, bname);
mrb_str_cat_cstr(mrb, buffer, extname);
if (suffix && *suffix) {
mrb_int blen = RSTRING_LEN(buffer);
mrb_int slen = strlen(suffix);
if (blen > slen && memcmp(RSTRING_PTR(buffer) + blen - slen, suffix, slen) == 0) {
mrb_str_resize(mrb, buffer, blen - slen);
}
}
return buffer;
#else
char *path;
const char *path;
const char *suffix = NULL;
mrb_get_args(mrb, "z|z", &path, &suffix);
// Copy path to a local buffer to avoid modifying the original string
size_t len = strlen(path);
if (len == 0) {
const char *endp = path + strlen(path);
if (path == endp) {
return mrb_str_new_lit(mrb, ".");
}
#ifdef _WIN32
if (UNC_PATH_P(path)) {
path += 2;
SKIP_DIRSEP(path);
NEXT_DIRSEP(path); // skip server name
SKIP_DIRSEP(path);
NEXT_DIRSEP(path); // skip share name
}
else if (DRIVE_LETTER_P(path)) {
path += 2;
if (path == endp) {
return mrb_str_new_lit(mrb, "");
}
}
#endif // _WIN32
// Remove trailing slashes (except when path is only "/")
while (len > 1 && path[len - 1] == '/') {
len--;
while (path < endp && DIRSEP_P(endp[-1])) {
endp--;
}
// Find the last path separator
ssize_t base = len - 1;
while (base >= 0 && path[base] != '/') {
const char *base = endp;
while (path < base && !DIRSEP_P(base[-1])) {
base--;
}
base++; // move to the first character after '/'
// If path is all slashes, return "/"
if ((size_t)base == len) {
if (base == endp) {
return mrb_str_new_lit(mrb, "/");
}
mrb_value result = mrb_str_new(mrb, path + base, len - base);
// Suffix removal (CRuby compatible)
if (suffix && *suffix) {
mrb_int blen = RSTRING_LEN(result);
mrb_int blen = endp - base;
mrb_int slen = strlen(suffix);
if (blen > slen && memcmp(RSTRING_PTR(result) + blen - slen, suffix, slen) == 0) {
mrb_str_resize(mrb, result, blen - slen);
if (blen > slen && memcmp(endp - slen, suffix, slen) == 0) {
endp -= slen;
}
}
return result;
#endif
return mrb_str_new(mrb, base, endp - base);
}
/*
+34
View File
@@ -57,6 +57,40 @@ assert('File.basename with suffix') do
assert_equal 'foo.rb', File.basename('foo.rb', '.RB') # case-sensitive
end
if MRubyIOTestUtil.win?
assert('File.basename (for Windows)') do
assert_equal '/', File.basename('/')
assert_equal '/', File.basename('//a')
assert_equal '/', File.basename('//a/')
assert_equal '/', File.basename('//a/b')
assert_equal '/', File.basename('//a/b/')
assert_equal 'c', File.basename('//a/b/c')
assert_equal 'c', File.basename('//a/b/c/')
assert_equal '/', File.basename("\\\\a\\b")
assert_equal '', File.basename('c:')
assert_equal '/', File.basename('c:/')
assert_equal 'a', File.basename('c:/a')
assert_equal 'a', File.basename('c:/a/')
assert_equal 'b', File.basename('c:/a/b')
assert_equal '/', File.basename("c:\\")
end
else
assert('File.basename (for generic)') do
assert_equal '/', File.basename('/')
assert_equal 'a', File.basename('//a')
assert_equal 'a', File.basename('//a/')
assert_equal 'b', File.basename('//a/b')
assert_equal 'b', File.basename('//a/b/')
assert_equal 'c', File.basename('//a/b/c')
assert_equal 'c', File.basename('//a/b/c/')
assert_equal 'c:', File.basename('c:')
assert_equal 'c:', File.basename('c:/')
assert_equal 'a', File.basename('c:/a')
assert_equal 'a', File.basename('c:/a/')
assert_equal 'b', File.basename('c:/a/b')
end
end
assert('File.dirname') do
assert_equal '.', File.dirname('')
assert_equal '.', File.dirname('a')