mirror of
https://github.com/mruby/mruby
synced 2026-06-08 16:11:16 +00:00
Reimplement the File.dirname method
The purpose is as follows:
- Stop using `mrb_locale_from_utf8()`.
- Because there is no corresponding `mrb_utf8_from_locale()`.
- Because on Windows, for example, if the code page is 932 (CP932, likely ShiftJIS), it cannot be distinguished from the second byte 0x5c (\), and returns wrong results.
- Stop using `dirname(3)`.
- Because leading consecutive slashes are not truncated.
For example, if `/////a/b` is given, CRuby returns `/a`, but mruby so far returns `/////a`.
- Because the `path` argument cannot be passed in an immutable form.
- Stop using `_splitpath()` in the Windows implementation.
- Because there is no support for UNC paths with up to 32767 characters.
ref. https://learn.microsoft.com/ja-jp/dotnet/standard/io/file-path-formats#unc-paths
- Because modifying the result of paths terminated by a directory separator.
Previously, for example, `C:/` would return `C:.` instead of `C:/`, and `a/b/` would return `a/b` instead of `a`.
This commit is contained in:
+53
-29
@@ -51,8 +51,10 @@
|
||||
#define PATH_SEPARATOR ";"
|
||||
#define FILE_ALT_SEPARATOR "\\"
|
||||
#define VOLUME_SEPARATOR ":"
|
||||
#define DIRSEP_P(ch) (((ch) == '/') | ((ch) == '\\'))
|
||||
#else
|
||||
#define PATH_SEPARATOR ":"
|
||||
#define DIRSEP_P(ch) ((ch) == '/')
|
||||
#endif
|
||||
|
||||
#ifndef LOCK_SH
|
||||
@@ -163,42 +165,64 @@ mrb_file_s_rename(mrb_state *mrb, mrb_value obj)
|
||||
return mrb_fixnum_value(0);
|
||||
}
|
||||
|
||||
#define SKIP_DIRSEP(p) for (; DIRSEP_P(*(p)); (p)++)
|
||||
#define NEXT_DIRSEP(p) for (; *(p) != '\0' && !DIRSEP_P(*(p)); (p)++)
|
||||
|
||||
static const char*
|
||||
scan_dirname(const char *path)
|
||||
{
|
||||
const char *p = path + strlen(path);
|
||||
for (; p > path && DIRSEP_P(p[-1]); p--)
|
||||
;
|
||||
for (; p > path && !DIRSEP_P(p[-1]); p--)
|
||||
;
|
||||
for (; p > path && DIRSEP_P(p[-1]); p--)
|
||||
;
|
||||
return p > path ? p : path;
|
||||
}
|
||||
|
||||
static mrb_value
|
||||
mrb_file_dirname(mrb_state *mrb, mrb_value klass)
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
char dname[_MAX_DIR], vname[_MAX_DRIVE];
|
||||
char buffer[_MAX_DRIVE + _MAX_DIR];
|
||||
const char *utf8_path;
|
||||
mrb_get_args(mrb, "z", &utf8_path);
|
||||
char *path = mrb_locale_from_utf8(utf8_path, -1);
|
||||
_splitpath(path, vname, dname, NULL, NULL);
|
||||
snprintf(buffer, _MAX_DRIVE + _MAX_DIR, "%s%s", vname, dname);
|
||||
mrb_locale_free(path);
|
||||
size_t ridx = strlen(buffer);
|
||||
if (ridx == 0) {
|
||||
strncpy(buffer, ".", 2); /* null terminated */
|
||||
}
|
||||
else if (ridx > 1) {
|
||||
ridx--;
|
||||
while (ridx > 0 && (buffer[ridx] == '/' || buffer[ridx] == '\\')) {
|
||||
buffer[ridx] = '\0'; /* remove last char */
|
||||
ridx--;
|
||||
const char *path;
|
||||
mrb_get_args(mrb, "z", &path);
|
||||
|
||||
const char *p = path;
|
||||
#ifdef _WIN32
|
||||
if (DIRSEP_P(p[0]) && DIRSEP_P(p[1])) {
|
||||
p += 2;
|
||||
SKIP_DIRSEP(p);
|
||||
path = p - 2; /* if consecutive, point to the trailing slash */
|
||||
NEXT_DIRSEP(p);
|
||||
const char *o = p;
|
||||
SKIP_DIRSEP(p);
|
||||
if (*p == '\0') {
|
||||
p = o;
|
||||
}
|
||||
else {
|
||||
NEXT_DIRSEP(p);
|
||||
p = scan_dirname(p);
|
||||
}
|
||||
return mrb_str_new(mrb, path, p - path);
|
||||
}
|
||||
return mrb_str_new_cstr(mrb, buffer);
|
||||
#else
|
||||
mrb_value s;
|
||||
mrb_get_args(mrb, "S", &s);
|
||||
char *path = mrb_locale_from_utf8(mrb_str_to_cstr(mrb, s), -1);
|
||||
char *dname;
|
||||
if ((dname = dirname(path)) == NULL) {
|
||||
mrb_locale_free(path);
|
||||
mrb_sys_fail(mrb, "dirname");
|
||||
else if (ISALPHA(p[0]) && p[1] == ':') {
|
||||
p += 2;
|
||||
const char *o = p;
|
||||
SKIP_DIRSEP(p);
|
||||
p = scan_dirname(p);
|
||||
mrb_value s = mrb_str_new(mrb, path, p - path);
|
||||
if (p == o) {
|
||||
mrb_str_cat_lit(mrb, s, ".");
|
||||
}
|
||||
return s;
|
||||
}
|
||||
mrb_locale_free(path);
|
||||
return mrb_str_new_cstr(mrb, dname);
|
||||
#endif
|
||||
SKIP_DIRSEP(p);
|
||||
if (p > path) {
|
||||
path = p - 1; /* if consecutive, point to the trailing slash */
|
||||
}
|
||||
p = scan_dirname(p);
|
||||
return (p == path) ? mrb_str_new_lit(mrb, ".") : mrb_str_new(mrb, path, p - path);
|
||||
}
|
||||
|
||||
static mrb_value
|
||||
|
||||
@@ -40,8 +40,48 @@ assert('File.dirname') do
|
||||
assert_equal '.', File.dirname('')
|
||||
assert_equal '.', File.dirname('a')
|
||||
assert_equal '/', File.dirname('/a')
|
||||
assert_equal '/', File.dirname('/a/')
|
||||
assert_equal 'a', File.dirname('a/b')
|
||||
assert_equal 'a', File.dirname('a/b/')
|
||||
assert_equal 'a/b', File.dirname('a/b/c')
|
||||
assert_equal '/a', File.dirname('/a/b')
|
||||
assert_equal '/a', File.dirname('/a/b/')
|
||||
assert_equal '/a/b', File.dirname('/a/b/c')
|
||||
assert_equal '/a/b', File.dirname('/a/b/c/')
|
||||
assert_equal '/', File.dirname('/a//')
|
||||
assert_equal '/a', File.dirname('/a//b')
|
||||
assert_equal '/a/b', File.dirname('/a/b//c//')
|
||||
end
|
||||
|
||||
unless MRubyIOTestUtil.win?
|
||||
assert('File.dirname (not Windows)') do
|
||||
assert_equal '/a', File.dirname('//a//b/')
|
||||
end
|
||||
else
|
||||
assert('File.dirname (on Windows)') do
|
||||
assert_equal 'c:.', File.dirname('c:')
|
||||
assert_equal 'c:.', File.dirname('c:a')
|
||||
assert_equal 'c:.', File.dirname('c:a/')
|
||||
assert_equal 'c:a', File.dirname('c:a/b')
|
||||
assert_equal 'c:/', File.dirname('c:/')
|
||||
assert_equal 'c:/', File.dirname('c:/a')
|
||||
assert_equal 'c:/', File.dirname('c:/a/')
|
||||
assert_equal 'c:/a', File.dirname('c:/a/b')
|
||||
assert_equal '//.', File.dirname('//.')
|
||||
assert_equal '//.', File.dirname('//./')
|
||||
assert_equal '//./a', File.dirname('//./a')
|
||||
assert_equal '//./a', File.dirname('//./a/')
|
||||
assert_equal '//./a', File.dirname('//./a/b')
|
||||
assert_equal '//./a/b', File.dirname('//./a/b/c')
|
||||
assert_equal '//?', File.dirname('//?/')
|
||||
assert_equal '//?/a', File.dirname('//?/a')
|
||||
assert_equal '//?/a', File.dirname('//?/a/')
|
||||
assert_equal '//?/a', File.dirname('//?/a/b')
|
||||
assert_equal '//host1', File.dirname('//host1/')
|
||||
assert_equal '//host1/a', File.dirname('//host1/a')
|
||||
assert_equal '//host1/a', File.dirname('//host1/a/')
|
||||
assert_equal '//host1/a', File.dirname('//host1/a/b')
|
||||
end
|
||||
end
|
||||
|
||||
assert('File.extname') do
|
||||
|
||||
Reference in New Issue
Block a user