ruby-changes:4927
From: ko1@a...
Date: Thu, 15 May 2008 15:34:29 +0900 (JST)
Subject: [ruby-changes:4927] nobu - Ruby:r16420 (trunk, ruby_1_8): * file.c (file_expand_path): support for alternative data stream
nobu 2008-05-15 15:34:02 +0900 (Thu, 15 May 2008) New Revision: 16420 Modified files: branches/ruby_1_8/ChangeLog branches/ruby_1_8/defines.h branches/ruby_1_8/file.c branches/ruby_1_8/version.h trunk/ChangeLog trunk/dir.c trunk/file.c trunk/include/ruby/defines.h trunk/test/ruby/test_file_exhaustive.rb trunk/version.h Log: * file.c (file_expand_path): support for alternative data stream and ignored trailing garbages of NTFS. * file.c (rb_file_s_basename): ditto. * file.c (rb_file_s_extname): ditto. http://svn.ruby-lang.org/cgi-bin/viewvc.cgi/trunk/test/ruby/test_file_exhaustive.rb?r1=16420&r2=16419&diff_format=u http://svn.ruby-lang.org/cgi-bin/viewvc.cgi/branches/ruby_1_8/defines.h?r1=16420&r2=16419&diff_format=u http://svn.ruby-lang.org/cgi-bin/viewvc.cgi/trunk/version.h?r1=16420&r2=16419&diff_format=u http://svn.ruby-lang.org/cgi-bin/viewvc.cgi/trunk/file.c?r1=16420&r2=16419&diff_format=u http://svn.ruby-lang.org/cgi-bin/viewvc.cgi/branches/ruby_1_8/ChangeLog?r1=16420&r2=16419&diff_format=u http://svn.ruby-lang.org/cgi-bin/viewvc.cgi/trunk/ChangeLog?r1=16420&r2=16419&diff_format=u http://svn.ruby-lang.org/cgi-bin/viewvc.cgi/branches/ruby_1_8/file.c?r1=16420&r2=16419&diff_format=u http://svn.ruby-lang.org/cgi-bin/viewvc.cgi/trunk/dir.c?r1=16420&r2=16419&diff_format=u http://svn.ruby-lang.org/cgi-bin/viewvc.cgi/branches/ruby_1_8/version.h?r1=16420&r2=16419&diff_format=u http://svn.ruby-lang.org/cgi-bin/viewvc.cgi/trunk/include/ruby/defines.h?r1=16420&r2=16419&diff_format=u Index: include/ruby/defines.h =================================================================== --- include/ruby/defines.h (revision 16419) +++ include/ruby/defines.h (revision 16420) @@ -262,6 +262,14 @@ #define ENV_IGNORECASE #endif +#ifndef CASEFOLD_FILESYSTEM +# if defined DOSISH || defined __VMS +# define CASEFOLD_FILESYSTEM 1 +# else +# define CASEFOLD_FILESYSTEM 0 +# endif +#endif + #ifndef DLEXT_MAXLEN #define DLEXT_MAXLEN 4 #endif Index: ChangeLog =================================================================== --- ChangeLog (revision 16419) +++ ChangeLog (revision 16420) @@ -1,3 +1,12 @@ +Thu May 15 15:33:59 2008 Nobuyoshi Nakada <nobu@r...> + + * file.c (file_expand_path): support for alternative data stream + and ignored trailing garbages of NTFS. + + * file.c (rb_file_s_basename): ditto. + + * file.c (rb_file_s_extname): ditto. + Wed May 14 22:09:25 2008 Yusuke Endoh <mame@t...> * ChangeLog: fix typo. Index: dir.c =================================================================== --- dir.c (revision 16419) +++ dir.c (revision 16420) @@ -66,14 +66,6 @@ #define lstat stat #endif -#ifndef CASEFOLD_FILESYSTEM -# if defined DOSISH || defined __VMS -# define CASEFOLD_FILESYSTEM 1 -# else -# define CASEFOLD_FILESYSTEM 0 -# endif -#endif - #define FNM_NOESCAPE 0x01 #define FNM_PATHNAME 0x02 #define FNM_DOTMATCH 0x04 Index: version.h =================================================================== --- version.h (revision 16419) +++ version.h (revision 16420) @@ -1,7 +1,7 @@ #define RUBY_VERSION "1.9.0" -#define RUBY_RELEASE_DATE "2008-05-14" +#define RUBY_RELEASE_DATE "2008-05-15" #define RUBY_VERSION_CODE 190 -#define RUBY_RELEASE_CODE 20080514 +#define RUBY_RELEASE_CODE 20080515 #define RUBY_PATCHLEVEL 0 #define RUBY_VERSION_MAJOR 1 @@ -9,7 +9,7 @@ #define RUBY_VERSION_TEENY 0 #define RUBY_RELEASE_YEAR 2008 #define RUBY_RELEASE_MONTH 5 -#define RUBY_RELEASE_DAY 14 +#define RUBY_RELEASE_DAY 15 #ifdef RUBY_EXTERN RUBY_EXTERN const char ruby_version[]; Index: test/ruby/test_file_exhaustive.rb =================================================================== --- test/ruby/test_file_exhaustive.rb (revision 16419) +++ test/ruby/test_file_exhaustive.rb (revision 16420) @@ -374,6 +374,11 @@ def test_expand_path assert_equal(@file, File.expand_path(File.basename(@file), File.dirname(@file))) + if /cygwin|mingw|mswin|bccwin/ =~ RUBY_PLATFORM + assert_equal(@file, File.expand_path(@file + " ")) + assert_equal(@file, File.expand_path(@file + ".")) + assert_equal(@file, File.expand_path(@file + "::$DATA")) + end end def test_basename @@ -383,6 +388,19 @@ assert_equal("foo", File.basename("foo", ".ext")) assert_equal("foo", File.basename("foo.ext", ".ext")) assert_equal("foo", File.basename("foo.ext", ".*")) + if /cygwin|mingw|mswin|bccwin/ =~ RUBY_PLATFORM + basename = File.basename(@file) + assert_equal(basename, File.basename(@file + " ")) + assert_equal(basename, File.basename(@file + ".")) + assert_equal(basename, File.basename(@file + "::$DATA")) + basename.chomp!(".test") + assert_equal(basename, File.basename(@file + " ", ".test")) + assert_equal(basename, File.basename(@file + ".", ".test")) + assert_equal(basename, File.basename(@file + "::$DATA", ".test")) + assert_equal(basename, File.basename(@file + " ", ".*")) + assert_equal(basename, File.basename(@file + ".", ".*")) + assert_equal(basename, File.basename(@file + "::$DATA", ".*")) + end end def test_dirname @@ -394,6 +412,13 @@ assert(".test", File.extname(@file)) assert_equal("", File.extname("foo")) assert_equal("", File.extname("")) + if /cygwin|mingw|mswin|bccwin/ =~ RUBY_PLATFORM + assert_equal("", File.extname("foo ")) + assert_equal(".ext", File.extname("foo.ext ")) + assert_equal(".ext", File.extname("foo.ext.")) + assert_equal(".ext", File.extname("foo.ext::$DATA")) + assert_equal("", File.extname("foo::$DATA.ext")) + end end def test_split Index: file.c =================================================================== --- file.c (revision 16419) +++ file.c (revision 16420) @@ -14,6 +14,10 @@ #ifdef _WIN32 #include "missing/file.h" #endif +#ifdef __CYGWIN__ +#include <windows.h> +#include <sys/cygwin.h> +#endif #include "ruby/ruby.h" #include "ruby/io.h" @@ -2399,6 +2403,18 @@ #define isdirsep(x) ((x) == '/') #endif +#if defined _WIN32 || defined __CYGWIN__ +#define USE_NTFS 1 +#else +#define USE_NTFS 0 +#endif + +#if USE_NTFS +#define istrailinggabage(x) ((x) == '.' || (x) == ' ') +#else +#define istrailinggabage(x) 0 +#endif + #ifndef CharNext /* defined as CharNext[AW] on Windows. */ # if defined(DJGPP) # define CharNext(p) ((p) + mblen(p, RUBY_MBCHAR_MAXSIZE)) @@ -2535,6 +2551,30 @@ return chompdirsep(path); } +#if USE_NTFS +static char * +ntfs_tail(const char *path) +{ + while (*path && *path != ':') { + if (istrailinggabage(*path)) { + const char *last = path++; + while (istrailinggabage(*path)) path++; + if (!*path || *path == ':') return (char *)last; + } + else if (isdirsep(*path)) { + const char *last = path++; + while (isdirsep(*path)) path++; + if (!*path) return (char *)last; + if (*path == ':') path++; + } + else { + path = CharNext(path); + } + } + return (char *)path; +} +#endif + #define BUFCHECK(cond) do {\ long bdiff = p - buf;\ while (cond) {\ @@ -2717,6 +2757,11 @@ } b = ++s; } +#if USE_NTFS + else { + do *++s; while (istrailinggabage(*s)); + } +#endif break; case '/': #if defined DOSISH || defined __CYGWIN__ @@ -2729,6 +2774,19 @@ break; } } +#if USE_NTFS + else { + --s; + case ' ': { + const char *e = s; + while (istrailinggabage(*s)) s++; + if (!*s) { + s = e; + goto endpath; + } + } + } +#endif break; case '/': #if defined DOSISH || defined __CYGWIN__ @@ -2751,14 +2809,75 @@ } if (s > b) { +#if USE_NTFS + endpath: + if (s > b + 6 && strncasecmp(s - 6, ":$DATA", 6) == 0) { + /* alias of stream */ + /* get rid of a bug of x64 VC++ */ + if (*(s-7) == ':') s -= 7; /* prime */ + else if (memchr(b, ':', s - 6 - b)) s -= 6; /* alternative */ + } +#endif BUFCHECK(bdiff + (s-b) >= buflen); memcpy(++p, b, s-b); p += s-b; } if (p == skiproot(buf) - 1) p++; + buflen = p - buf; +#if USE_NTFS + *p = '\0'; + if (!strpbrk(b = buf, "*?")) { + size_t len; + WIN32_FIND_DATA wfd; +#ifdef __CYGWIN__ + int lnk_added = 0; + struct stat st; + char w32buf[MAXPATHLEN], sep = 0; + p = 0; + if (lstat(buf, &st) == 0 && S_ISLNK(st.st_mode)) { + p = strrdirsep(buf); + if (!p) p = skipprefix(buf); + if (p) { + sep = *p; + *p = '\0'; + } + } + if (cygwin_conv_to_win32_path(buf, w32buf) == 0) { + b = w32buf; + } + if (p) *p = sep; + else p = buf; + if (b == w32buf) { + strlcat(w32buf, p, sizeof(w32buf)); + len = strlen(p); + if (len > 4 && STRCASECMP(p + len - 4, ".lnk") != 0) { + lnk_added = 1; + strlcat(w32buf, ".lnk", sizeof(w32buf)); + } + } +#endif + HANDLE h = FindFirstFile(b, &wfd); + if (h != INVALID_HANDLE_VALUE) { + FindClose(h); + p = strrdirsep(buf); + len = strlen(wfd.cFileName); +#ifdef __CYGWIN__ + if (lnk_added && len > 4 && + STRCASECMP(wfd.cFileName + len - 4, ".lnk") == 0) { + len -= 4; + } +#endif + if (!p) p = buf; + buflen = ++p - buf + len; + rb_str_resize(result, buflen); + memcpy(p, wfd.cFileName, len + 1); + } + } +#endif + if (tainted) OBJ_TAINT(result); - rb_str_set_len(result, p - buf); + rb_str_set_len(result, buflen); rb_enc_check(fname, result); return result; } @@ -2800,22 +2919,29 @@ } static int -rmext(const char *p, const char *e) +rmext(const char *p, int l1, const char *e) { - int l1, l2; + int l2; if (!e) return 0; - l1 = chompdirsep(p) - p; l2 = strlen(e); if (l2 == 2 && e[1] == '*') { - e = strrchr(p, *e); - if (!e) return 0; + unsigned char c = *e; + e = p + l1; + do { + if (e <= p) return 0; + } while (*--e != c); return e - p; } if (l1 < l2) return l1; - if (strncmp(p+l1-l2, e, l2) == 0) { +#if CASEFOLD_FILESYSTEM +#define fncomp strncasecmp +#else +#define fncomp strncmp +#endif + if (fncomp(p+l1-l2, e, l2) == 0) { return l1-l2; } return 0; @@ -2843,7 +2969,7 @@ #if defined DOSISH_DRIVE_LETTER || defined DOSISH_UNC char *root; #endif - int f; + int f, n; if (rb_scan_args(argc, argv, "11", &fname, &fext) == 2) { StringValue(fext); @@ -2877,18 +3003,22 @@ #endif #endif } - else if (!(p = strrdirsep(name))) { - if (NIL_P(fext) || !(f = rmext(name, StringValueCStr(fext)))) { - f = chompdirsep(name) - name; - if (f == RSTRING_LEN(fname)) return fname; - } - p = name; - } else { - while (isdirsep(*p)) p++; /* skip last / */ - if (NIL_P(fext) || !(f = rmext(p, StringValueCStr(fext)))) { - f = chompdirsep(p) - p; + if (!(p = strrdirsep(name))) { + p = name; } + else { + while (isdirsep(*p)) p++; /* skip last / */ + } +#if USE_NTFS + n = ntfs_tail(p) - p; +#else + n = chompdirsep(p) - p; +#endif + if (NIL_P(fext) || !(f = rmext(p, n, StringValueCStr(fext)))) { + f = n; + } + if (f == RSTRING_LEN(fname)) return fname; } basename = rb_str_new(p, f); rb_enc_copy(basename, fname); @@ -2965,21 +3095,48 @@ static VALUE rb_file_s_extname(VALUE klass, VALUE fname) { - char *name, *p, *e; + const char *name, *p, *e; VALUE extname; FilePathStringValue(fname); name = StringValueCStr(fname); p = strrdirsep(name); /* get the last path component */ if (!p) - p = name; + p = name; else - p++; - - e = strrchr(p, '.'); /* get the last dot of the last component */ - if (!e || e == p || !e[1]) /* no dot, or the only dot is first or end? */ + p++; + + e = 0; + while (*p) { + if (*p == '.' || istrailinggabage(*p)) { +#if USE_NTFS + const char *last = p++, *dot = last; + while (istrailinggabage(*p)) { + if (*p == '.') dot = p; + p++; + } + if (!*p || *p == ':') { + p = last; + break; + } + e = dot; + continue; +#else + e = p; /* get the last dot of the last component */ +#endif + } +#if USE_NTFS + else if (*p == ':') { + break; + } +#endif + else if (isdirsep(*p)) + break; + p = CharNext(p); + } + if (!e || e+1 == p) /* no dot, or the only dot is first or end? */ return rb_str_new(0, 0); - extname = rb_str_new(e, chompdirsep(e) - e); /* keep the dot, too! */ + extname = rb_str_new(e, p - e); /* keep the dot, too! */ rb_enc_copy(extname, fname); OBJ_INFECT(extname, fname); return extname; Index: ruby_1_8/ChangeLog =================================================================== --- ruby_1_8/ChangeLog (revision 16419) +++ ruby_1_8/ChangeLog (revision 16420) @@ -1,3 +1,12 @@ +Thu May 15 15:33:59 2008 Nobuyoshi Nakada <nobu@r...> + + * file.c (file_expand_path): support for alternative data stream + and ignored trailing garbages of NTFS. + + * file.c (rb_file_s_basename): ditto. + + * file.c (rb_file_s_extname): ditto. + Wed May 14 19:24:59 2008 Akinori MUSHA <knu@i...> * array.c (rb_ary_count): Override Enumerable#count for better Index: ruby_1_8/version.h =================================================================== --- ruby_1_8/version.h (revision 16419) +++ ruby_1_8/version.h (revision 16420) @@ -1,7 +1,7 @@ #define RUBY_VERSION "1.8.7" -#define RUBY_RELEASE_DATE "2008-05-14" +#define RUBY_RELEASE_DATE "2008-05-15" #define RUBY_VERSION_CODE 187 -#define RUBY_RELEASE_CODE 20080514 +#define RUBY_RELEASE_CODE 20080515 #define RUBY_PATCHLEVEL 5000 #define RUBY_VERSION_MAJOR 1 @@ -9,7 +9,7 @@ #define RUBY_VERSION_TEENY 7 #define RUBY_RELEASE_YEAR 2008 #define RUBY_RELEASE_MONTH 5 -#define RUBY_RELEASE_DAY 14 +#define RUBY_RELEASE_DAY 15 #ifdef RUBY_EXTERN RUBY_EXTERN const char ruby_version[]; Index: ruby_1_8/defines.h =================================================================== --- ruby_1_8/defines.h (revision 16419) +++ ruby_1_8/defines.h (revision 16420) @@ -254,6 +254,14 @@ #define ENV_IGNORECASE #endif +#ifndef CASEFOLD_FILESYSTEM +# if defined DOSISH || defined __VMS +# define CASEFOLD_FILESYSTEM 1 +# else +# define CASEFOLD_FILESYSTEM 0 +# endif +#endif + #ifndef DLEXT_MAXLEN #define DLEXT_MAXLEN 4 #endif Index: ruby_1_8/file.c =================================================================== --- ruby_1_8/file.c (revision 16419) +++ ruby_1_8/file.c (revision 16420) @@ -15,6 +15,10 @@ #ifdef _WIN32 #include "missing/file.h" #endif +#ifdef __CYGWIN__ +#include <windows.h> +#include <sys/cygwin.h> +#endif #include "ruby.h" #include "rubyio.h" @@ -2310,6 +2314,18 @@ #define isdirsep(x) ((x) == '/') #endif +#if defined _WIN32 || defined __CYGWIN__ +#define USE_NTFS 1 +#else +#define USE_NTFS 0 +#endif + +#if USE_NTFS +#define istrailinggabage(x) ((x) == '.' || (x) == ' ') +#else +#define istrailinggabage(x) 0 +#endif + #ifndef CharNext /* defined as CharNext[AW] on Windows. */ # if defined(DJGPP) # define CharNext(p) ((p) + mblen(p, MB_CUR_MAX)) @@ -2454,6 +2470,30 @@ return chompdirsep(path); } +#if USE_NTFS +static char * +ntfs_tail(const char *path) +{ + while (*path && *path != ':') { + if (istrailinggabage(*path)) { + const char *last = path++; + while (istrailinggabage(*path)) path++; + if (!*path || *path == ':') return (char *)last; + } + else if (isdirsep(*path)) { + const char *last = path++; + while (isdirsep(*path)) path++; + if (!*path) return (char *)last; + if (*path == ':') path++; + } + else { + path = CharNext(path); + } + } + return (char *)path; +} +#endif + #define BUFCHECK(cond) do {\ long bdiff = p - buf;\ while (cond) {\ @@ -2480,7 +2520,8 @@ file_expand_path(fname, dname, result) VALUE fname, dname, result; { - char *s, *buf, *b, *p, *pend, *root; + const char *s, *b; + char *buf, *p, *pend, *root; long buflen, dirlen; int tainted; @@ -2621,15 +2662,21 @@ case '.': if (*(s+1) == '\0' || isdirsep(*(s+1))) { /* We must go back to the parent */ + char *n; *p = '\0'; - if (!(b = strrdirsep(root))) { + if (!(n = strrdirsep(root))) { *p = '/'; } else { - p = b; + p = n; } b = ++s; } +#if USE_NTFS + else { + do *++s; while (istrailinggabage(*s)); + } +#endif break; case '/': #if defined DOSISH || defined __CYGWIN__ @@ -2642,6 +2689,19 @@ break; } } +#if USE_NTFS + else { + --s; + case ' ': { + const char *e = s; + while (istrailinggabage(*s)) s++; + if (!*s) { + s = e; + goto endpath; + } + } + } +#endif break; case '/': #if defined DOSISH || defined __CYGWIN__ @@ -2664,15 +2724,75 @@ } if (s > b) { +#if USE_NTFS + endpath: + if (s > b + 6 && strncasecmp(s - 6, ":$DATA", 6) == 0) { + /* alias of stream */ + /* get rid of a bug of x64 VC++ */ + if (*(s-7) == ':') s -= 7; /* prime */ + else if (memchr(b, ':', s - 6 - b)) s -= 6; /* alternative */ + } +#endif BUFCHECK(bdiff + (s-b) >= buflen); memcpy(++p, b, s-b); p += s-b; } if (p == skiproot(buf) - 1) p++; + buflen = p - buf; +#if USE_NTFS + *p = '\0'; + if (!strpbrk(b = buf, "*?")) { + size_t len; + WIN32_FIND_DATA wfd; +#ifdef __CYGWIN__ + int lnk_added = 0; + struct stat st; + char w32buf[MAXPATHLEN], sep = 0; + p = 0; + if (lstat(buf, &st) == 0 && S_ISLNK(st.st_mode)) { + p = strrdirsep(buf); + if (!p) p = skipprefix(buf); + if (p) { + sep = *p; + *p = '\0'; + } + } + if (cygwin_conv_to_win32_path(buf, w32buf) == 0) { + b = w32buf; + } + if (p) *p = sep; + else p = buf; + if (b == w32buf) { + strlcat(w32buf, p, sizeof(w32buf)); + len = strlen(p); + if (len > 4 && strcasecmp(p + len - 4, ".lnk") != 0) { + lnk_added = 1; + strlcat(w32buf, ".lnk", sizeof(w32buf)); + } + } +#endif + HANDLE h = FindFirstFile(b, &wfd); + if (h != INVALID_HANDLE_VALUE) { + FindClose(h); + p = strrdirsep(buf); + len = strlen(wfd.cFileName); +#ifdef __CYGWIN__ + if (lnk_added && len > 4 && + strcasecmp(wfd.cFileName + len - 4, ".lnk") == 0) { + len -= 4; + } +#endif + if (!p) p = buf; + buflen = ++p - buf + len; + rb_str_resize(result, buflen); + memcpy(p, wfd.cFileName, len + 1); + } + } +#endif + if (tainted) OBJ_TAINT(result); - RSTRING(result)->len = p - buf; - *p = '\0'; + rb_str_set_len(result, buflen); return result; } @@ -2716,23 +2836,31 @@ } static int -rmext(p, e) +rmext(p, l1, e) const char *p, *e; + int l1; { - int l1, l2; + int l2; if (!e) return 0; - l1 = chompdirsep(p) - p; l2 = strlen(e); if (l2 == 2 && e[1] == '*') { - e = strrchr(p, *e); - if (!e) return 0; + unsigned char c = *e; + e = p + l1; + do { + if (e <= p) return 0; + } while (*--e != c); return e - p; } if (l1 < l2) return l1; - if (strncmp(p+l1-l2, e, l2) == 0) { +#if CASEFOLD_FILESYSTEM +#define fncomp strncasecmp +#else +#define fncomp strncmp +#endif + if (fncomp(p+l1-l2, e, l2) == 0) { return l1-l2; } return 0; @@ -2762,7 +2890,7 @@ #if defined DOSISH_DRIVE_LETTER || defined DOSISH_UNC char *root; #endif - int f; + int f, n; if (rb_scan_args(argc, argv, "11", &fname, &fext) == 2) { StringValue(fext); @@ -2796,18 +2924,22 @@ #endif #endif } - else if (!(p = strrdirsep(name))) { - if (NIL_P(fext) || !(f = rmext(name, StringValueCStr(fext)))) { - f = chompdirsep(name) - name; - if (f == RSTRING(fname)->len) return fname; - } - p = name; - } else { - while (isdirsep(*p)) p++; /* skip last / */ - if (NIL_P(fext) || !(f = rmext(p, StringValueCStr(fext)))) { - f = chompdirsep(p) - p; + if (!(p = strrdirsep(name))) { + p = name; } + else { + while (isdirsep(*p)) p++; /* skip last / */ + } +#if USE_NTFS + n = ntfs_tail(p) - p; +#else + n = chompdirsep(p) - p; +#endif + if (NIL_P(fext) || !(f = rmext(p, n, StringValueCStr(fext)))) { + f = n; + } + if (f == RSTRING_LEN(fname)) return fname; } basename = rb_str_new(p, f); OBJ_INFECT(basename, fname); @@ -2883,22 +3015,49 @@ rb_file_s_extname(klass, fname) VALUE klass, fname; { - char *name, *p, *e; + const char *name, *p, *e; VALUE extname; name = StringValueCStr(fname); p = strrdirsep(name); /* get the last path component */ if (!p) - p = name; + p = name; else - p++; - - e = strrchr(p, '.'); /* get the last dot of the last component */ - if (!e || e == p || !e[1]) /* no dot, or the only dot is first or end? */ - return rb_str_new2(""); - extname = rb_str_new(e, chompdirsep(e) - e); /* keep the dot, too! */ - OBJ_INFECT(extname, fname); - return extname; + p++; + + e = 0; + while (*p) { + if (*p == '.' || istrailinggabage(*p)) { +#if USE_NTFS + const char *last = p++, *dot = last; + while (istrailinggabage(*p)) { + if (*p == '.') dot = p; + p++; + } + if (!*p || *p == ':') { + p = last; + break; + } + e = dot; + continue; +#else + e = p; /* get the last dot of the last component */ +#endif + } +#if USE_NTFS + else if (*p == ':') { + break; + } +#endif + else if (isdirsep(*p)) + break; + p = CharNext(p); + } + if (!e || e+1 == p) /* no dot, or the only dot is first or end? */ + return rb_str_new(0, 0); + extname = rb_str_new(e, p - e); /* keep the dot, too! */ + OBJ_INFECT(extname, fname); + return extname; } /* -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/