diff options
Diffstat (limited to 'pathname.c')
| -rw-r--r-- | pathname.c | 372 |
1 files changed, 372 insertions, 0 deletions
diff --git a/pathname.c b/pathname.c new file mode 100644 index 0000000000..b12f640373 --- /dev/null +++ b/pathname.c @@ -0,0 +1,372 @@ +#include "ruby.h" +#include "internal.h" +#include "internal/file.h" +#include "internal/string.h" +#include "internal/vm.h" + +#if defined __CYGWIN__ || defined DOSISH +# define drive_letter 1 +# define alt_separator 1 +# define isdirsep(x) ((x) == '/' || (x) == '\\') +#else +# define drive_letter 0 +# define alt_separator 0 +# define isdirsep(x) ((x) == '/') +#endif + +static VALUE rb_cPathname; +static ID id_at_path; +static ID id_sub; + +static VALUE +check_strpath(VALUE path) +{ + Check_Type(path, T_STRING); + rb_get_path_check_no_convert(path); + return path; +} + +static VALUE +get_strpath(VALUE obj) +{ + VALUE strpath; + strpath = rb_ivar_get(obj, id_at_path); + if (!RB_TYPE_P(strpath, T_STRING)) + rb_raise(rb_eTypeError, "unexpected @path"); + rb_get_path_check_no_convert(strpath); + return strpath; +} + +/* + * call-seq: + * self <=> other -> -1, 0, 1, or nil + * + * Compares the contents of +self+ and +other+ as strings; + * see String#<=>. + * + * Returns: + * + * - <tt>-1</tt> if +self+'s string is smaller than +other+'s string. + * - <tt>0</tt> if the two are equal. + * - <tt>1</tt> if +self+'s string is larger than +other+'s string. + * - <tt>nil</tt> if +other+ is not a \Pathname. + * + * Examples: + * + * Pathname('a') <=> Pathname('b') # => -1 + * Pathname('a') <=> Pathname('ab') # => -1 + * Pathname('a') <=> Pathname('a') # => 0 + * Pathname('b') <=> Pathname('a') # => 1 + * Pathname('ab') <=> Pathname('a') # => 1 + * Pathname('ab') <=> 'a' # => nil + * + * Two pathnames that are different may refer to the same entry in the filesystem: + * + * Pathname('lib') <=> Pathname('./lib') # => 1 + * + */ +static VALUE +path_cmp(VALUE self, VALUE other) +{ + VALUE s1, s2; + char *p1, *p2; + char *e1, *e2; + if (!rb_obj_is_kind_of(other, rb_cPathname)) + return Qnil; + s1 = get_strpath(self); + s2 = get_strpath(other); + p1 = RSTRING_PTR(s1); + p2 = RSTRING_PTR(s2); + e1 = p1 + RSTRING_LEN(s1); + e2 = p2 + RSTRING_LEN(s2); + while (p1 < e1 && p2 < e2) { + int c1, c2; + c1 = (unsigned char)*p1++; + c2 = (unsigned char)*p2++; + if (c1 == '/') c1 = '\0'; + if (c2 == '/') c2 = '\0'; + if (c1 != c2) { + if (c1 < c2) + return INT2FIX(-1); + else + return INT2FIX(1); + } + } + if (p1 < e1) + return INT2FIX(1); + if (p2 < e2) + return INT2FIX(-1); + return INT2FIX(0); +} + +/* + * Return a pathname which is substituted by String#sub. + * + * path1 = Pathname.new('/usr/bin/perl') + * path1.sub('perl', 'ruby') + * #=> #<Pathname:/usr/bin/ruby> + */ +static VALUE +path_sub(int argc, VALUE *argv, VALUE self) +{ + VALUE str = get_strpath(self); + + if (rb_block_given_p()) { + str = rb_block_call(str, id_sub, argc, argv, 0, 0); + } + else { + str = rb_funcallv(str, id_sub, argc, argv); + } + return rb_class_new_instance(1, &str, rb_obj_class(self)); +} + +/* :nodoc: */ +static VALUE +same_paths(VALUE self, VALUE a, VALUE b) +{ + check_strpath(a); + check_strpath(b); + if (CASEFOLD_FILESYSTEM) + return RBOOL(rb_str_casecmp(a, b) == INT2FIX(0)); + else + return rb_str_equal(a, b); +} + +/* + * Predicate method for root directories. Returns +true+ if the + * pathname consists of consecutive slashes. + * + * It doesn't access the filesystem. So it may return +false+ for some + * pathnames which points to roots such as <tt>/usr/..</tt>. + */ +static VALUE +path_root_p(VALUE self) +{ + VALUE path = get_strpath(self); + if (RSTRING_LEN(path) == 0) return Qfalse; + const char *ptr = RSTRING_PTR(path), *end = RSTRING_END(path); + rb_encoding *enc = rb_enc_get(path); + const char *base = rb_enc_path_skip_prefix_root(ptr, end, enc); + return RBOOL(base == end); +} + +/* + * call-seq: + * absolute? -> true or false + * + * Returns whether +self+ contains an absolute path: + * + * Pathname('/home').absolute? # => true + * Pathname('lib').absolute? # => false + * + * The result is OS-dependent for some paths: + * + * Pathname('C:/').absolute? # => true # On Windows. + * Pathname('C:/').absolute? # => false # Elsewhere. + * + */ +static VALUE +path_absolute_p(VALUE self) +{ + VALUE path = get_strpath(self); + const char *ptr = RSTRING_PTR(path); + long len = RSTRING_LEN(path); + if (len < 1) return Qfalse; + if (drive_letter) { + if (len >= 2 && ISALPHA(ptr[0]) && (ptr[1] == ':')) return Qtrue; + } + return RBOOL(isdirsep(ptr[0])); +} + +/* :nodoc: */ +static VALUE +has_separator_p(VALUE self, VALUE path) +{ + const char *ptr = RSTRING_PTR(check_strpath(path)); + const char *end = RSTRING_END(path); + if (alt_separator) { + rb_encoding *enc = rb_enc_get(path); + bool mb = !rb_str_enc_fastpath(path); + while (ptr < end) { + if (isdirsep(*ptr)) return Qtrue; + ptr += (mb ? rb_enc_mbclen(ptr, end, enc) : 1); + } + } + else { + /* assume '/' will never be trailing bytes */ + if (memchr(ptr, '/', end - ptr)) return Qtrue; + } + return Qfalse; +} + +/* + * Return a pathname with +repl+ added as a suffix to the basename. + * + * If self has no extension part, +repl+ is appended. + * + * Pathname.new('/usr/bin/shutdown').sub_ext('.rb') + * #=> #<Pathname:/usr/bin/shutdown.rb> + */ +static VALUE +path_sub_ext(VALUE self, VALUE repl) +{ + VALUE path = get_strpath(self); + long len = RSTRING_LEN(path); + const char *ptr = RSTRING_PTR(path); + const char *ext = ruby_enc_find_extname(ptr, &len, rb_enc_get(path)); + if (len > 0) { + RUBY_ASSERT(ext, "should point the last dot"); + path = rb_str_subseq(path, 0, ext - ptr); + } + else { + /* no dot or dotted file */ + path = rb_str_dup(path); + } + path = rb_str_append(path, repl); + return rb_class_new_instance(1, &path, rb_obj_class(self)); +} + +/* :nodoc: */ +/* chop_basename(path) -> [pre-basename, basename] or nil */ +static VALUE +chop_basename(VALUE self, VALUE path) +{ + long baselen, alllen = RSTRING_LEN(check_strpath(path)); + if (alllen <= 0) return Qnil; + rb_encoding *enc = rb_enc_get(path); + const char *name = RSTRING_PTR(path); + const char *base = ruby_enc_find_basename(name, &baselen, &alllen, enc); + if (baselen < 1) return Qnil; + if (baselen == 1 && isdirsep(*base)) return Qnil; + RUBY_ASSERT(base >= name); + RUBY_ASSERT(base <= RSTRING_END(path)); + VALUE dir = rb_str_subseq(path, 0, base - name); + VALUE basename = rb_enc_str_new(base, alllen, enc); + RB_GC_GUARD(path); + return rb_assoc_new(dir, basename); +} + +/* :nodoc: */ +/* split_names(path) -> prefix, [name, ...] */ +static VALUE +split_names(VALUE self, VALUE path) +{ + rb_encoding *enc = rb_enc_get(check_strpath(path)); + const char *beg = RSTRING_PTR(path), *ptr = beg; + const char *end = RSTRING_END(path); + const char *root = rb_enc_path_skip_prefix_root(ptr, end, enc); + VALUE pre = rb_str_subseq(path, 0, root - ptr); + VALUE names = rb_ary_new(); + while (ptr < end) { + const char *next = rb_enc_path_next(ptr, end, enc); + if (next > ptr) rb_ary_push(names, rb_str_subseq(path, ptr - beg, next - ptr)); + ptr = next; + while (ptr < end && isdirsep(*ptr)) ++ptr; + } + return rb_assoc_new(pre, names); +} + +/* :nodoc: */ +/* has_trailing_separator?(path) -> bool */ +static VALUE +has_trailing_separator(VALUE self, VALUE path) +{ + long baselen, alllen = RSTRING_LEN(check_strpath(path)); + if (alllen <= 0) return Qfalse; + rb_encoding *enc = rb_enc_get(path); + const char *name = RSTRING_PTR(path); + const char *base = ruby_enc_find_basename(name, &baselen, &alllen, enc); + if (baselen < 1) return Qfalse; + if (baselen == 1 && isdirsep(*base)) return Qfalse; + return RBOOL(base + alllen < RSTRING_END(path)); +} + +/* :nodoc: */ +/* add_trailing_separator(path) -> path */ +static VALUE +add_trailing_separator(VALUE self, VALUE path) +{ + if (RSTRING_LEN(check_strpath(path)) <= 0) return path; + rb_encoding *enc = rb_enc_get(path); + const char *name = RSTRING_PTR(path); + const char *end = RSTRING_END(path); + const char *top = rb_enc_path_skip_prefix(name, end, enc); + if (top < end && isdirsep(end[-1])) { + if (end[-1] == '/' || rb_enc_prev_char(top, end, end, enc) == end - 1) + return path; + } + return rb_str_cat_cstr(rb_str_dup(path), "/"); +} + +/* :nodoc: */ +static VALUE +del_trailing_separator(VALUE self, VALUE path) +{ + long len = RSTRING_LEN(check_strpath(path)); + if (len <= 0) return path; + rb_encoding *enc = rb_enc_get(path); + const char *name = RSTRING_PTR(path); + const char *end = name + len, *tail = end; + const char *top = rb_enc_path_skip_prefix(name, end, enc); + if (tail > top && isdirsep(tail[-1])) { + while (--tail > top && isdirsep(tail[-1])); + if (tail > top && + tail[0] != '/' && + !rb_str_enc_fastpath(path) && + rb_enc_left_char_head(top, tail, end, enc) != tail) { + /* trailing byte, not a directory separator */ + ++tail; + } + if (tail < end) { + if (tail == name || (drive_letter && tail == top && top[-1] == ':')) { + ++tail; + } + } + } + if (tail == end) return path; + return rb_str_subseq(path, 0, tail - name); +} + +#include "pathname_builtin.rbinc" + +static void init_ids(void); + +void +Init_pathname(void) +{ +#ifdef HAVE_RB_EXT_RACTOR_SAFE + rb_ext_ractor_safe(true); +#endif + + init_ids(); + InitVM(pathname); +} + +void +InitVM_pathname(void) +{ + rb_cPathname = rb_define_class("Pathname", rb_cObject); + rb_define_method(rb_cPathname, "<=>", path_cmp, 1); + rb_define_method(rb_cPathname, "sub", path_sub, -1); + rb_define_method(rb_cPathname, "sub_ext", path_sub_ext, 1); + rb_define_method(rb_cPathname, "root?", path_root_p, 0); + rb_define_method(rb_cPathname, "absolute?", path_absolute_p, 0); + + rb_define_private_method(rb_cPathname, "same_paths?", same_paths, 2); + rb_define_private_method(rb_cPathname, "has_separator?", has_separator_p, 1); + rb_define_private_method(rb_cPathname, "chop_basename", chop_basename, 1); + rb_define_private_method(rb_cPathname, "split_names", split_names, 1); + rb_define_private_method(rb_cPathname, "has_trailing_separator?", has_trailing_separator, 1); + rb_define_private_method(rb_cPathname, "add_trailing_separator", add_trailing_separator, 1); + rb_define_private_method(rb_cPathname, "del_trailing_separator", del_trailing_separator, 1); + + rb_provide("pathname.so"); +} + +void +init_ids(void) +{ +#undef rb_intern + id_at_path = rb_intern("@path"); + id_sub = rb_intern("sub"); +} |
