Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- require 'fileutils'
- require 'iterator'
- module Kernel
- def Path(str)
- Path === str ? str : Path.new(str)
- end
- end
- class String
- def to_path(separator="/")
- Path.new(self, separator)
- end
- end
- # you can use Path same as strings for hash-lookups, but not as keys
- # Path is immutable
- # FIXME: ::_load, #_dump for Marshal
- class Path
- TruePattern = Object.new
- def TruePattern.===(other); true; end
- def TruePattern.=~(other); true; end
- include Comparable
- class <<self
- # returns the current working directory
- def current; new(Dir.getwd); end
- # returns the path of the current script (ENV["_"] if $0 doesn't exist)
- def program
- if File.exist?($0)
- new($0)
- elsif ENV.has_key?("_")
- new(ENV["_"])
- else
- nil
- end
- end
- # home environment variable if set, else nil
- def home
- ENV.has_key?("HOME") ? new(ENV["HOME"].to_str) : nil
- end
- end
- # always use "/" as separator, no matter on what platform
- def initialize(path, separator="/")
- raise ArgumentError, "Illegal null character in path #{path.inspect}" if /\0/ =~ path
- @pathname = path.to_str
- @separator = separator
- @segments = @pathname.scan(/[^#{Regexp.escape(@separator)}]+/)
- @top = (@segments.length == 0 ? @pathname : @pathname[0, @pathname.index(@segments[0])])
- taint if path.tainted?
- end
- # returns a String
- def pathname
- @pathname.dup
- end
- # returns a String
- def dirname
- File.dirname(@pathname)
- end
- # returns a String, if without_suffix is given (can be an array), it is removed
- def filename(without_suffix=nil)
- File.basename(@pathname, without_suffix)
- end
- alias name filename
- def extname
- File.extname(@pathname)
- end
- # returns a Path
- def path
- dup
- end
- # returns the Path of the containing directory
- def directory
- self.class.new(File.dirname(@pathname), @separator)
- end
- # returns the Path of the last segment, if without_suffix is
- # given (can be an array), it is removed
- def file(without_suffix=nil)
- self.class.new(File.basename(@pathname, without_suffix), @separator)
- end
- def segments
- @pathname.scan(/[^#{Regexp.escape(@separator)}]+/)
- end
- # checks whether two paths are refering the same file,
- # basically a.same_file?(b) is a short for Path(a).expanded.clean == Path(b).expanded.clean.
- # does not access file system, also see real_same?
- def same?(path, separator=@separator)
- expanded.clean == self.class.new(path, separator).expanded.clean
- end
- # checks if this path (assumed to be a directory) is base to other path
- # does not access file system, also see real_include?
- def include?(path, separator=@separator)
- self.class.new(path, separator).expanded.clean.index(expanded.clean) == 0
- end
- # checks whether two paths are refering the same file,
- # basically a.real_same_file?(b) is a short for Path(a).real == Path(b).real.
- # accesses file system, also see same?
- def real_same?(path, separator=@separator)
- real == self.class.new(path, separator).real
- end
- # checks if this path (must be a directory) is base to other path
- # accesses file system, also see include?
- def real_include?(path, separator=@separator)
- directory? && self.class.new(path, separator).real.index(real) == 0
- end
- # Path#+ appends a path fragment to this one to produce a new Path object.
- # also see Path#join
- #
- # p1 = Path("/usr") # <Path /usr>
- # p2 = p1 + "bin/ruby" # <Path /usr/bin/ruby>
- # p3 = p1 + "../bin/ruby" # <Path /bin/ruby>
- # p4 = p1 + "bin/../ruby" # <Path /usr/bin/../ruby>
- # p5 = p1 + "/etc/passwd" # raises ArgumentError
- #
- # This method doesn't access the file system; it is pure string manipulation.
- def +(other)
- other = other.to_str
- segments = other.scan(/[^#{Regexp.escape(@separator)}]+/)
- top = (segments.length == 0 ? other : other[0, other.index(segments[0])])
- return self.class.new(other, @separator) unless top.empty?
- current = @segments.dup
- while segments.last =~ /\A\.\.?\z/
- current.pop if segments.shift == ".."
- end
- self.class.new(@top + (current + segments).join(@separator), @separator)
- end
- def join(*others)
- self+others.join(@separator)
- end
- # returns start of the path if absolute, else nil
- # absolute paths start with either the path separator or <drive> ":" <path sep>
- # (windows' "C:\")
- def absolute?
- if @pathname =~ /\A([^#{Regexp.escape(@separator)}]+:#{Regexp.escape(@separator)})/ ||
- @pathname =~ /\A(#{Regexp.escape(@separator)})/ then
- $1
- else
- nil
- end
- end
- # returns true or false
- def relative?
- !absolute?
- end
- # true if file/directory exists and its size is zero or it's entries only . and ..
- def empty?
- if File.directory?(@pathname)
- Dir.entries(@pathname).all? { |x| x == '.' || x == '..' }
- else
- File.size(@pathname) == 0
- end
- end
- # prepends the current directory or home if path starts with ~
- def expanded
- absolute? ? dup : self.class.new(File.expand_path(@pathname), @separator)
- end
- def relative(to=self.class.current)
- to = self.class.new(to.to_str) unless to.class == self.class #to.respond_to?(:to_a) - to_a at the moment is implemented in Object, thus a useless test
- return dup if relative? && to == self.class.current
- segments = self.expanded.segments
- other = to.expanded.segments
- while other[0] == segments[0] && other.length > 0
- other.shift
- segments.shift
- end
- segments.unshift(*([".."]*other.length))
- self.class.new(segments.join(@separator),@separator)
- end
- # Clean the path simply by resolving and removing excess "." and ".." entries.
- # Pure stringmanipulation.
- def clean(consider_symlink=false)
- if consider_symlink then
- cleanpath_conservative
- else
- cleanpath_aggressive
- end
- end
- # cleanpath_aggressive assumes:
- # * no symlink
- # * all path prefix contained in the path is existing directory
- def cleanpath_aggressive
- return self.class.new('', @separator) if @pathname == ''
- absolute = absolute?
- names = []
- @segments.each { |name|
- next if name == '.'
- if name == '..'
- if names.empty?
- next if absolute
- else
- if names.last != '..'
- names.pop
- next
- end
- end
- end
- names << name
- }
- return self.class.new(absolute ? absolute : '.', @separator) if names.empty?
- path = absolute ? absolute : ''
- path << names.join(@separator)
- self.class.new(path, @separator)
- end
- private :cleanpath_aggressive
- def cleanpath_conservative
- return self.class.new('', @separator) if @pathname == ''
- names = @segments.dup
- absolute = absolute?
- last_dot = names.last == '.'
- names.delete('.')
- names.shift while names.first == '..' if absolute
- return self.class.new(absolute ? absolute : '.', @separator) if names.empty?
- path = absolute ? absolute : ''
- path << names.join(@separator)
- if names.last != '..'
- if last_dot
- path << @separator+'.'
- elsif %r{#{@separator}\z} =~ @pathname
- path << @separator
- end
- end
- self.class.new(path, @separator)
- end
- private :cleanpath_conservative
- # helper for realpath, get's top + segments (top beeing "" for non absolutes and "/" on *nix,
- # something like "C:\" on win or whatever else absolute? returns. segments beeing a list
- # of segments besides top
- def top_segments #:nodoc:
- top = absolute? || ""
- if top then
- path = @pathname
- else
- path = self.class.current+@pathname
- top = path.absolute? || ""
- path = path.to_str
- end
- segments = path[top.length..-1].scan(/[^#{Regexp.escape(@separator)}]+/)
- return top, segments
- end
- # Returns a real (absolute) path of +self+ in the actual filesystem.
- # The real path doesn't contain symlinks or useless dots.
- # Set resolve_nonexisting to true if you don't want it to raise an error
- # if a non existing path is resolved.
- # ∆ check for symlink loops?
- def real(resolve_nonexisting=false)
- top, unresolved = expanded.top_segments
- resolved = []
- inexistent = []
- if resolve_nonexisting
- while !File.exist?(top + unresolved.join(@separator)) && !unresolved.empty?
- inexistent.unshift unresolved.pop
- end
- end
- # raise Errno::ELOOP.new(path) if loop_check[symlink_id]
- # go from right to left, append resolved symlinks
- until unresolved.empty?
- case unresolved.last
- when '.'
- unresolved.pop
- when '..'
- resolved.unshift(unresolved.pop)
- else
- if (File.lstat(path=top+unresolved.join(@separator)).symlink?) then
- real = self.class.new(File.readlink(path))
- if real.absolute? then
- top, unresolved = *real.top_segments
- else
- unresolved[-1,1] = real.segments
- end
- else
- resolved.unshift(unresolved.pop)
- end
- end #case
- end
- real_path = self.class.new(top+resolved.join(@separator), @separator)
- real_path += inexistent.join(@separator)
- real_path
- end
- # creates a directory and all intermediary directories
- def create
- FileUtils.mkpath(@pathname)
- end
- # creates a file in path, creates intermediary directories if necessary
- # warning, if the file already exists it will be deleted
- def create_file(delete_existing=false)
- directory.create
- delete if exists? and delete_existing
- FileUtils.touch(@pathname) unless exists?
- end
- # removes file or directory (and all it's contents)
- def delete
- if File.directory?(@pathname)
- FileUtils.rm_r(@pathname)
- else
- File.unlink(@pathname)
- end
- end
- # yield each line of a file, if no block is given
- # returns an Iterator over the lines.
- def lines(sep_string=$/, &block)
- if block then
- File.open(@pathname, "rb") { |fh| fh.each_line(sep_string, &block) }
- else
- Iterator.new { |iter|
- File.open(@pathname, "rb") { |fh|
- fh.each_line(sep_string) { |line| iter.yield(line) }
- }
- }
- end
- end
- # yields chunks of size bytes, if no block is given returns
- # an Iterator over those chunks.
- def chunks(size=4096, &block)
- if block then
- File.open(@pathname, "rb") { |fh|
- while line = fh.read(size)
- yield(line)
- end
- }
- else
- Iterator.new { |iter|
- File.open(@pathname, "rb") { |fh|
- while line = fh.read(size)
- iter.yield(line)
- end
- }
- }
- end
- end
- # See +IO.read+
- def read(*args, &block); File.read(@pathname, *args, &block); end
- # See +IO.open+
- def open(*args, &block); File.open(@pathname, *args, &block); end
- # Write text to file and close it
- def write(text); File.open(@pathname, "wb") { |fh| fh.write(text) }; end
- # Append text to file and close it
- def append(text); File.open(@pathname, "ab") { |fh| fh.write(text) }; end
- # Truncates the file _file_name_ to be at most _integer_ bytes long.
- # Not available on all platforms.
- def truncate(integer); File.truncate(@pathname, integer); end
- # See +IO.sysopen+
- def sysopen(*args, &block); File.sysopen(@pathname, *args, &block); end
- # all entries in a directory, except . and ..
- # if no block is given it will return an Iterator over the entries
- def entries(pattern=nil)
- pattern ||= TruePattern
- if block_given? then
- Dir.entries(@pathname).each { |entry|
- next if entry == "." or entry == ".."
- path = self+entry
- yield(path) if pattern =~ entry
- }
- else
- Iterator.new { |iter|
- Dir.entries(@pathname).each { |entry|
- next if entry == "." or entry == ".."
- path = self+entry
- iter.yield(path) if pattern =~ entry
- }
- }
- end
- end
- # if a block is given it will yield all files
- # matching the optional pattern, else it will return an Iterator (Enumerable)
- # over those.
- def files(pattern=nil)
- pattern ||= TruePattern
- if block_given? then
- Dir.entries(@pathname).each { |entry|
- path = self+entry
- yield(path) if pattern =~ entry and path.file?
- }
- else
- Iterator.new { |iter|
- Dir.entries(@pathname).each { |entry|
- path = self+entry
- iter.yield(path) if pattern =~ entry and path.file?
- }
- }
- end
- end
- # if a block is given it will yield all directories (except . and ..)
- # matching the optional pattern, else it will return an Iterator (Enumerable)
- # over those.
- def directories(pattern=nil)
- pattern ||= TruePattern
- if block_given? then
- Dir.entries(@pathname).each { |entry|
- next if entry == "." or entry == ".."
- path = self+entry
- yield(path) if pattern =~ entry and path.directory?
- }
- else
- Iterator.new { |iter|
- Dir.entries(@pathname).each { |entry|
- next if entry == "." or entry == ".."
- path = self+entry
- iter.yield(path) if pattern =~ entry and path.directory?
- }
- }
- end
- end
- # Returns the last access time for the file as a Time object.
- # If parameter format is given, strftime is applied on the Time
- def accessed(format=nil)
- format ? File.atime(@pathname).strftime(format) : File.atime(@pathname)
- end
- # Returns the change time for the file (the time at which directory
- # information about the file was changed, not the file itself).
- # If parameter format is given, strftime is applied on the Time
- def changed(format=nil)
- format ? File.ctime(@pathname).strftime(format) : File.ctime(@pathname)
- end
- # Returns the modification time for the named file as a Time object.
- # If parameter format is given, strftime is applied on the Time
- def modified(format=nil)
- format ? File.mtime(@pathname).strftime(format) : File.mtime(@pathname)
- end
- # See <tt>File.utime</tt>. Update the access and modification times.
- def utime(atime, mtime) File.utime(atime, mtime, @pathname) end
- # Returns a +File::Stat+ object for the named file (see +File::Stat+).
- def stat; File.stat(@pathname); end
- # Same as +Path::stat+, but does not follow the last symbolic link.
- # Instead, reports on the link itself.
- def lstat; File.lstat(@pathname); end
- # Identifies the type of the named file; the return string is one of
- # "+file+", "+directory+", "+characterSpecial+", "+blockSpecial+",
- # "+fifo+", "+link+", "+socket+", or "+unknown+".
- def type; File.ftype(@pathname); end
- # See <tt>File.chmod</tt>. Changes permissions.
- def chmod(mode) File.chmod(mode, @pathname) end
- # See <tt>File.lchmod</tt>.
- def lchmod(mode) File.lchmod(mode, @pathname) end
- # See <tt>File.chown</tt>. Change owner and group of file.
- def chown(owner, group) File.chown(owner, group, @pathname) end
- # See <tt>File.lchown</tt>.
- def lchown(owner, group) File.lchown(owner, group, @pathname) end
- # See <tt>File.fnmatch?</tt>. Return +true+ if the receiver matches the given
- # pattern.
- def fnmatch?(pattern, *args) File.fnmatch?(pattern, @pathname, *args) end
- # See <tt>File.link</tt>. Creates a hard link.
- def link(from) File.link(@pathname, from) end
- # See <tt>File.readlink</tt>. Read symbolic link.
- def readlink() self.class.new(File.readlink(@pathname), @separator) end
- # See <tt>File.symlink</tt>. Creates a symbolic link in from
- def symlink(from) File.symlink(@pathname, from) end
- # See <tt>File.blockdev?</tt>.
- def blockdev?; File.blockdev?(@pathname); end
- # See <tt>File.chardev?</tt>.
- def chardev?; File.chardev?(@pathname); end
- # See <tt>File.executable?</tt>.
- def executable?; File.executable?(@pathname); end
- # See <tt>File.executable_real?</tt>.
- def executable_real?; File.executable_real?(@pathname); end
- # See <tt>File.exist?</tt>.
- def exist?; File.exist?(@pathname); end
- # See <tt>File.exist?</tt>.
- def exists?; File.exist?(@pathname); end
- # See <tt>File.grpowned?</tt>.
- def grpowned?; File.grpowned?(@pathname); end
- # See <tt>File.directory?</tt>.
- def directory?; File.directory?(@pathname); end
- # See <tt>File.file?</tt>.
- def file?; File.file?(@pathname); end
- # See <tt>File.pipe?</tt>.
- def pipe?; File.pipe?(@pathname); end
- # See <tt>File.socket?</tt>.
- def socket?; File.socket?(@pathname); end
- # See <tt>File.readable?</tt>.
- def readable?; File.readable?(@pathname); end
- # See <tt>File.readable_real?</tt>.
- def readable_real?; File.readable_real?(@pathname); end
- # See <tt>File.setuid?</tt>.
- def setuid?; File.setuid?(@pathname); end
- # See <tt>File.setgid?</tt>.
- def setgid?; File.setgid?(@pathname); end
- # See <tt>File.size</tt>.
- def size; File.size(@pathname); end
- # See <tt>File.size?</tt>.
- def size?; File.size?(@pathname); end
- # See <tt>File.sticky?</tt>.
- def sticky?; File.sticky?(@pathname); end
- # See <tt>File.symlink?</tt>.
- def symlink?; File.symlink?(@pathname); end
- # See <tt>File.writable?</tt>.
- def writable?; File.writable?(@pathname); end
- # See <tt>File.writable_real?</tt>.
- def writable_real?; File.writable_real?(@pathname); end
- # will print the path, will print '.' for empty paths.
- def to_str
- @pathname.empty? ? "." : @pathname.dup
- end
- alias to_s to_str
- def inspect # :nodoc:
- "<Path #{@pathname}>"
- end
- def to_path
- dup
- end
- def dup
- self.class.new(@pathname, @separator)
- end
- def hash # :nodoc:
- @pathname.hash
- end
- # compares on string basis, can be compared to a String
- def <=>(other)
- other.to_str.gsub(@separator, 0.chr) <=> @pathname.gsub(@separator, 0.chr)
- end
- # compares on string basis, can be compared to a String
- def ==(other)
- other.to_str == @pathname
- end
- def eql?(other) # :nodoc:
- other.to_str.eql?(@pathname)
- end
- def taint # :nodoc:
- super
- @pathname.taint
- @segments.taint
- @segments.map { |e| e.taint }
- self
- end
- def untaint # :nodoc:
- super
- @pathname.untaint
- @segments.untaint
- @segments.map { |e| e.untaint }
- end
- end
Add Comment
Please, Sign In to add comment