From: Nobuyoshi Nakada Date: 2008-10-14T20:31:12+09:00 Subject: [ruby-core:19328] Re: [Feature #643] __DIR__ Hi, At Tue, 14 Oct 2008 04:20:18 +0900, Nikolai Weibull wrote in [ruby-core:19319]: > I realize that I'm responding to a joke, but wouldn't it perhaps be > nice if File.dirname took an optional second argument that allowed you > to specify how many levels up you want to go? I like this idea too. > I actually implemented this at work, simply because I was doing a lot > of nested File.dirname calls. This is my patch. Index: file.c =================================================================== --- file.c (revision 19773) +++ file.c (working copy) @@ -2523,4 +2523,32 @@ rb_path_last_separator(const char *path) static char * +path_last_nth_separator(const char *pstart, const char *pend, int nth) +{ + enum {lastmax = 16}; + char *lastbuf[lastmax]; + + if (nth <= 0) return (char *)pend; + do { + int lastidx = 0; + const char *path = pstart; + while (path < pend && *path) { + if (isdirsep(*path)) { + const char *tmp = path++; + while (isdirsep(*path)) path++; + if (!*path) break; + lastbuf[lastidx++ % lastmax] = (char *)tmp; + } + else { + path = CharNext(path); + } + } + if (lastidx < nth) return NULL; + if (nth <= lastmax) return lastbuf[(lastidx - nth) % lastmax]; + pend = lastbuf[lastidx % lastmax]; + } while ((nth -= lastmax) > 0); + return (char *)pend; +} + +static char * chompdirsep(const char *path) { @@ -3060,4 +3088,6 @@ rb_file_s_basename(int argc, VALUE *argv } +static VALUE rb_file_dirname(VALUE fname, int nth); + /* * call-seq: @@ -3073,11 +3103,22 @@ rb_file_s_basename(int argc, VALUE *argv static VALUE -rb_file_s_dirname(VALUE klass, VALUE fname) +rb_file_s_dirname(int argc, VALUE *argv, VALUE klass) +{ + VALUE fname, level; + int nth; + + rb_scan_args(argc, argv, "11", &fname, &level); + nth = NUM2INT(level); + FilePathStringValue(fname); + return rb_file_dirname(fname, nth); +} + +static VALUE +rb_file_dirname(VALUE fname, int nth) { const char *name, *root, *p; VALUE dirname; - FilePathStringValue(fname); - name = StringValueCStr(fname); + name = RSTRING_PTR(fname); root = skiproot(name); #ifdef DOSISH_UNC @@ -3088,5 +3129,21 @@ rb_file_s_dirname(VALUE klass, VALUE fna name = root - 1; #endif - p = strrdirsep(root); + if (nth < 0) { + const char *tmp = p = root; + while (++nth < 0) { + while (isdirsep(*tmp)) tmp++; + if (!(tmp = nextdirsep(p = tmp))) break; + p = tmp; + } + } + else if (nth == 0) { + p = chompdirsep(root); + } + else if (nth == 1) { + p = strrdirsep(root); + } + else { + p = path_last_nth_separator(root, name + RSTRING_LEN(fname), nth); + } if (!p) { p = root; @@ -3209,5 +3266,5 @@ rb_file_s_split(VALUE klass, VALUE path) { FilePathStringValue(path); /* get rid of converting twice */ - return rb_assoc_new(rb_file_s_dirname(Qnil, path), rb_file_s_basename(1,&path)); + return rb_assoc_new(rb_file_dirname(path, 1), rb_file_s_basename(1,&path)); } @@ -4709,5 +4766,5 @@ Init_File(void) rb_define_singleton_method(rb_cFile, "absolute_path", rb_file_s_absolute_path, -1); rb_define_singleton_method(rb_cFile, "basename", rb_file_s_basename, -1); - rb_define_singleton_method(rb_cFile, "dirname", rb_file_s_dirname, 1); + rb_define_singleton_method(rb_cFile, "dirname", rb_file_s_dirname, -1); rb_define_singleton_method(rb_cFile, "extname", rb_file_s_extname, 1); rb_define_singleton_method(rb_cFile, "path", rb_file_s_path, 1); -- Nobu Nakada