diff --git a/ext/pathname/pathname.c b/ext/pathname/pathname.c index 10d055f..73be963 100644 --- a/ext/pathname/pathname.c +++ b/ext/pathname/pathname.c @@ -3,6 +3,7 @@ static VALUE rb_cPathname; static ID id_at_path; static ID id_sub; +static ID id_realdirpath; static VALUE get_strpath(VALUE obj) @@ -83,6 +84,22 @@ path_sub(int argc, VALUE *argv, VALUE self) return rb_class_new_instance(1, &str, rb_obj_class(self)); } +/* + * Returns the real (absolute) pathname of +self+ in the actual filesystem. + * + * Does not contain symlinks or useless dots, +..+ and +.+. + * + * The last component of the real pathname can be nonexistent. + */ +static VALUE +path_realdirpath(int argc, VALUE *argv, VALUE self) +{ + VALUE basedir, str; + rb_scan_args(argc, argv, "01", &basedir); + str = rb_funcall(rb_cFile, id_realdirpath, 2, get_strpath(self), basedir); + return rb_class_new_instance(1, &str, rb_obj_class(self)); +} + static void init_ids(void); void @@ -102,6 +119,7 @@ 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, "realdirpath", path_realdirpath, -1); } void @@ -110,4 +128,5 @@ init_ids(void) #undef rb_intern id_at_path = rb_intern("@path"); id_sub = rb_intern("sub"); + id_realdirpath = rb_intern("realdirpath"); } diff --git a/lib/pathname.rb b/lib/pathname.rb index 03edc42..404be1a 100644 --- a/lib/pathname.rb +++ b/lib/pathname.rb @@ -146,6 +146,12 @@ module ::Kernel # === File property and manipulation methods # # These methods are a facade for File: +# - #each_line(*args, &block) +# - #read(*args) +# - #binread(*args) +# - #readlines(*args) +# - #write(*args) +# - #binwrite(*args) # - #atime # - #birthtime # - #ctime @@ -186,14 +192,8 @@ module ::Kernel # # === IO # -# These methods are a facade for IO: -# - #each_line(*args, &block) -# - #read(*args) -# - #binread(*args) -# - #readlines(*args) +# This method is a facade for IO: # - #sysopen(*args) -# - #write(*args) -# - #binwrite(*args) # # === Utilities # @@ -214,6 +214,7 @@ module ::Kernel # class Pathname + # The version string. VERSION = "0.4.0" # :stopdoc: @@ -239,6 +240,9 @@ class Pathname # def initialize(path) path = path.to_path if path.respond_to? :to_path + + raise TypeError unless path.is_a?(String) # Compatibility for C version + if path.include?("\0") raise ArgumentError, "pathname contains \\0: #{path.inspect}" end @@ -338,14 +342,42 @@ def sub_ext(repl) end if File.dirname('A:') == 'A:.' # DOSish drive letter - ABSOLUTE_PATH = /\A(?:[A-Za-z]:|#{SEPARATOR_PAT})/o + ABSOLUTE_PATH = /\A(?:[A-Za-z]:|#{SEPARATOR_PAT})/ else - ABSOLUTE_PATH = /\A#{SEPARATOR_PAT}/o + ABSOLUTE_PATH = /\A#{SEPARATOR_PAT}/ end private_constant :ABSOLUTE_PATH # :startdoc: + # Creates a full path, including any intermediate directories that don't yet + # exist. + # + # See FileUtils.mkpath and FileUtils.mkdir_p + def mkpath(mode: nil) + path = @path == '/' ? @path : @path.chomp('/') + + stack = [] + until File.directory?(path) || File.dirname(path) == path + stack.push path + path = File.dirname(path) + end + + stack.reverse_each do |dir| + dir = dir == '/' ? dir : dir.chomp('/') + if mode + Dir.mkdir dir, mode + File.chmod mode, dir + else + Dir.mkdir dir + end + rescue SystemCallError + raise unless File.directory?(dir) + end + + self + end + # chop_basename(path) -> [pre-basename, basename] or nil def chop_basename(path) # :nodoc: base = File.basename(path) @@ -853,6 +885,11 @@ def relative_path_from(base_directory) end class Pathname # * IO * + # See IO.sysopen. + def sysopen(...) IO.sysopen(@path, ...) end +end + +class Pathname # * File * # # #each_line iterates over the line in the file. It yields a String object # for each line. @@ -860,34 +897,27 @@ class Pathname # * IO * # This method has existed since 1.8.1. # def each_line(...) # :yield: line - IO.foreach(@path, ...) + File.foreach(@path, ...) end - # See IO.read. Returns all data from the file, or the first +N+ bytes + # See File.read. Returns all data from the file, or the first +N+ bytes # if specified. - def read(...) IO.read(@path, ...) end + def read(...) File.read(@path, ...) end - # See IO.binread. Returns all the bytes from the file, or the first +N+ + # See File.binread. Returns all the bytes from the file, or the first +N+ # if specified. - def binread(...) IO.binread(@path, ...) end + def binread(...) File.binread(@path, ...) end - # See IO.readlines. Returns all the lines from the file. - def readlines(...) IO.readlines(@path, ...) end - - # See IO.sysopen. - def sysopen(...) IO.sysopen(@path, ...) end + # See File.readlines. Returns all the lines from the file. + def readlines(...) File.readlines(@path, ...) end # Writes +contents+ to the file. See File.write. - def write(...) IO.write(@path, ...) end + def write(...) File.write(@path, ...) end # Writes +contents+ to the file, opening it in binary mode. # # See File.binwrite. - def binwrite(...) IO.binwrite(@path, ...) end -end - - -class Pathname # * File * + def binwrite(...) File.binwrite(@path, ...) end # See File.atime. Returns last access time. def atime() File.atime(@path) end @@ -1157,16 +1187,6 @@ def find(ignore_error: true) # :yield: pathname class Pathname # * FileUtils * - # Creates a full path, including any intermediate directories that don't yet - # exist. - # - # See FileUtils.mkpath and FileUtils.mkdir_p - def mkpath(mode: nil) - require 'fileutils' - FileUtils.mkpath(@path, mode: mode) - self - end - # Recursively deletes a directory, including all directories beneath it. # # See FileUtils.rm_rf @@ -1216,8 +1236,12 @@ module Kernel # # This method is available since 1.8.5. def Pathname(path) # :doc: + Kernel.Pathname(path) + end + private :Pathname + + def self.Pathname(path) # Compatibility for C version return path if Pathname === path Pathname.new(path) end - private :Pathname end