class Path
Path: An object-oriented wrapper for files. (Combines useful methods from FileUtils, File
, Dir, and more!)
To create a path object, or array of path objects, throw whatever you want into Path[]:
These returns a single path object: passwd = Path["/etc/passwd"] also_passwd = Path["/etc"] / "passwd" # joins two paths parent_dir = Path["/usr/local/bin"] / ".." # joins two paths (up one dir) These return an array of path objects: pictures = Path["photos/*.{jpg,png}"] # globbing notes = Path["notes/2014/**/*.txt"] # recursive globbing everything = Path["/etc"].ls
Each Path
object has the following attributes, which can all be modified:
path => the absolute path, as a string filename => just the name and extension basename => just the filename (without extension) ext => just the extension dir => just the directory dirs => an array of directories
Some commonly used methods:
path.file? path.exists? path.dir? path.mtime path.xattrs path.symlink? path.broken_symlink? path.symlink_target path.executable? path.chmod(0o666)
Interesting examples:
Path["*.jpeg"].each { |path| path.rename(:ext=>"jpg") } # renames .jpeg to .jpg files = Path["/etc"].ls # all files in directory morefiles = Path["/etc"].ls_R # all files in directory tree Path["*.txt"].each(&:gzip!) Path["filename.txt"] << "Append data!" # appends data to a file string = Path["filename.txt"].read # read all file data into a string json = Path["filename.json"].read_json # read and parse JSON doc = Path["filename.html"].read_html # read and parse HTML xml = Path["filename.xml"].parse # figure out the format and parse it (as XML) Path["saved_data.marshal"].write(data.marshal) # Save your data! data = Path["saved_data.marshal"].unmarshal # Load your data! Path["unknown_file"].mimetype # sniff the file to determine its mimetype Path["unknown_file"].mimetype.image? # ...is this some kind of image? Path["otherdir/"].cd do # temporarily change to "otherdir/" p Path.ls end p Path.ls
The `Path#dirs` attribute is a split up version of the directory (eg: Path.dirs => [“usr”, “local”, “bin”]).
You can modify the dirs array to change subsets of the directory. Here's an example that finds out if you're in a git repo:
def inside_a_git_repo? path = Path.pwd # start at the current directory while path.dirs.any? if (path/".git").exists? return true else path.dirs.pop # go up one level end end false end
Swap two files:
a, b = Path["file_a", "file_b"] temp = a.with(:ext => a.ext+".swapping") # return a modified version of this object a.mv(temp) b.mv(a) temp.mv(b)
Paths can be created for existant and non-existant files.
To create a nonexistant path object that thinks it's a directory, just add a '/' at the end. (eg: Path).
Performance has been an important factor in Path's design, so doing crazy things with Path
usually doesn't kill performance. Go nuts!
Constants
- AUTOGENERATED_CLASS_METHODS
FileUtils-like class-method versions of instance methods (eg: `Path.mv(src, dest)`)
Note: Methods with cardinality 1 (`method/1`) are instance methods that take one parameter, and hence, class methods that take two parameters.
- BINARY_EXTENSION
- COMPRESSORS
zopening files
- PATH_SEPARATOR
- URI_RE
Attributes
The filename without an extension
The filename without an extension
The filename without an extension
The directories in the path, split into an array. (eg: ['usr', 'src', 'linux'])
The file extension, including the . (eg: “.mp3”)
The file extension, including the . (eg: “.mp3”)
The file extension, including the . (eg: “.mp3”)
Public Class Methods
# File lib/epitools/path.rb, line 162 def self.[](path) case path when Path path when String if path =~ URI_RE Path.new(path) else # TODO: highlight backgrounds of codeblocks to show indent level & put boxes (or rules?) around (between?) double-spaced regions path = Path.expand_path(path) unless path =~ /(^|[^\\])[\?\*\{\}]/ # contains unescaped glob chars? new(path) else glob(path) end end end end
Change into the directory “dest”. If a block is given, it changes into the directory for the duration of the block, then puts you back where you came from once the block is finished.
# File lib/epitools/path.rb, line 1552 def self.cd(dest) dest = Path[dest] raise "Can't 'cd' into #{dest}" unless dest.dir? if block_given? orig = pwd Dir.chdir(dest) result = yield dest Dir.chdir(orig) result else Dir.chdir(dest) dest end end
# File lib/epitools/path.rb, line 154 def self.escape(str) Shellwords.escape(str) end
Same as File.expand_path, except preserves the trailing '/'.
# File lib/epitools/path.rb, line 1495 def self.expand_path(orig_path) new_path = File.expand_path orig_path new_path << "/" if orig_path.endswith "/" new_path end
Read xattrs from file (requires “getfattr” to be in the path)
# File lib/epitools/path.rb, line 540 def self.getfattr(path) # # file: Scissor_Sisters_-_Invisible_Light.flv # user.m.options="-c" cmd = %w[getfattr -d -m - -e base64] + [path] attrs = {} IO.popen(cmd, "rb", :err=>[:child, :out]) do |io| io.each_line do |line| if line =~ /^([^=]+)=0s(.+)/ key = $1 value = $2.from_base64 # unpack base64 string # value = value.encode("UTF-8", "UTF-8") # set string's encoding to UTF-8 value = value.force_encoding("UTF-8").scrub # set string's encoding to UTF-8 # value = value.encode("UTF-8", "UTF-8") # set string's encoding to UTF-8 attrs[key] = value end end end attrs end
# File lib/epitools/path.rb, line 158 def self.glob(str, hints={}) Dir[str].map { |entry| new(entry, hints) } end
User's current home directory
# File lib/epitools/path.rb, line 1526 def self.home Path[ENV['HOME']] end
# File lib/epitools/path.rb, line 1575 def self.ln_s(src, dest) FileUtils.ln_s(src, dest) Path[dest] end
# File lib/epitools/path.rb, line 1571 def self.ls(path); Path[path].ls end
# File lib/epitools/path.rb, line 1573 def self.ls_r(path); Path[path].ls_r; end
Path.mkcd(path)
creates a path if it doesn't exist, and changes to it (temporarily, if a block is provided)
# File lib/epitools/path.rb, line 1125 def self.mkcd(path, &block) path = path.to_Path unless path.is_a? Path path.mkdir_p unless path.exists? raise "Error: #{path} couldn't be created." unless path.dir? self.cd(path, &block) end
Initializers
# File lib/epitools/path.rb, line 128 def self.new(*args) if args.first =~ URI_RE and self != Path::URI Path::URI.new(args.first) else super(*args) end end
# File lib/epitools/path.rb, line 136 def initialize(newpath, hints={}) send("path=", newpath, hints) # if hints[:unlink_when_garbage_collected] # backup_path = path.dup # puts "unlinking #{backup_path} after gc!" # ObjectSpace.define_finalizer self do |object_id| # File.unlink backup_path # end # end end
# File lib/epitools/path.rb, line 1542 def self.popd @@dir_stack ||= [pwd] @@dir_stack.pop end
# File lib/epitools/path.rb, line 1537 def self.pushd(destination) @@dir_stack ||= [] @@dir_stack.push pwd end
The current directory
# File lib/epitools/path.rb, line 1533 def self.pwd Path.new expand_path(Dir.pwd) end
Set xattrs on a file (requires “setfattr” to be in the path)
# File lib/epitools/path.rb, line 568 def self.setfattr(path, key, value) cmd = %w[setfattr] if value == nil # delete cmd += ["-x", key] else # set cmd += ["-n", key, "-v", value.to_s.strip] end cmd << path IO.popen(cmd, "rb", :err=>[:child, :out]) do |io| result = io.each_line.to_a error = {:cmd => cmd, :result => result.to_s}.inspect raise error if result.any? end end
Create a uniqely named directory in /tmp
# File lib/epitools/path.rb, line 1516 def self.tmpdir(prefix="tmp") t = tmpfile t.rm; t.mkdir # FIXME: These two operations should be made atomic t end
TODO: Remove the tempfile when the Path
object is garbage collected or freed.
# File lib/epitools/path.rb, line 1504 def self.tmpfile(prefix="tmp") path = Path.new(Tempfile.new(prefix).path, unlink_when_garbage_collected: true) yield path if block_given? path end
A clone of `/usr/bin/which`: pass in the name of a binary, and it'll search the PATH returning the absolute location of the binary if it exists, or `nil` otherwise.
(Note: If you pass more than one argument, it'll return an array of `Path`s instead of
a single path.)
# File lib/epitools/path.rb, line 1599 def self.which(bin, *extras) if extras.empty? ENV["PATH"].split(PATH_SEPARATOR).find do |path| result = (Path[path] / (bin + BINARY_EXTENSION)) return result if result.exists? end nil else ([bin] + extras).map { |bin| which(bin) } end end
Public Instance Methods
# File lib/epitools/path.rb, line 492 def <=>(other) case other when Path sort_attrs <=> other.sort_attrs when String path <=> other else raise "Invalid comparison: Path to #{other.class}" end end
# File lib/epitools/path.rb, line 503 def ==(other) self.path == other.to_s end
Match the full path against a regular expression
# File lib/epitools/path.rb, line 1338 def =~(pattern) to_s =~ pattern end
Retrieve one of this file's xattrs
# File lib/epitools/path.rb, line 620 def [](key) attrs[key] end
Set this file's xattr
# File lib/epitools/path.rb, line 627 def []=(key, value) Path.setfattr(path, key, value) @attrs = nil # clear cached xattrs end
Append data to this file (accepts a string, an IO
, or it can yield the file handle to a block.)
# File lib/epitools/path.rb, line 753 def append(data=nil) # FIXME: copy_stream might be inefficient if you're calling it a lot. Investigate! self.open("ab") do |f| if data and not block_given? if data.is_an? IO IO.copy_stream(data, f) else f.write(data) end else yield f end end self end
# File lib/epitools/path.rb, line 401 def atime lstat.atime end
# File lib/epitools/path.rb, line 405 def atime=(new_atime) File.utime(new_atime, mtime, path) @lstat = nil new_atime end
Return a hash of all of this file's xattrs. (Metadata key=>valuse pairs, supported by most modern filesystems.)
# File lib/epitools/path.rb, line 592 def attrs @attrs ||= Path.getfattr(path) end
Set this file's xattrs. (Optimized so that only changed attrs are written to disk.)
# File lib/epitools/path.rb, line 600 def attrs=(new_attrs) changes = attrs.diff(new_attrs) changes.each do |key, (old, new)| case new when String, Numeric, true, false, nil self[key] = new else if new.respond_to? :to_str self[key] = new.to_str else raise "Error: Can't use a #{new.class} as an xattr value. Try passing a String." end end end end
Rename this file, “filename.ext”, to “filename.ext.bak”. (Does not modify this Path
object.)
# File lib/epitools/path.rb, line 1094 def backup! rename(backup_file) end
Return a copy of this Path
with “.bak” at the end
# File lib/epitools/path.rb, line 1078 def backup_file with(:filename => filename+".bak") end
# File lib/epitools/path.rb, line 441 def broken_symlink? File.symlink?(path) and not File.exist?(path) end
Change into the directory. If a block is given, it changes into the directory for the duration of the block, then puts you back where you came from once the block is finished.
# File lib/epitools/path.rb, line 988 def cd(&block) Path.cd(path, &block) end
# File lib/epitools/path.rb, line 464 def child_of?(parent) parent.parent_of? self end
Same usage as `FileUtils.chmod` (because it just calls `FileUtils.chmod`)
eg:
path.chmod(0600) # mode bits in octal (can also be 0o600 in ruby) path.chmod "u=wrx,go=rx", 'somecommand' path.chmod "u=wr,go=rr", "my.rb", "your.rb", "his.rb", "her.rb" path.chmod "ugo=rwx", "slutfile" path.chmod "u=wrx,g=rx,o=rx", '/usr/bin/ruby', :verbose => true
Letter things:
"a" :: is user, group, other mask. "u" :: is user's mask. "g" :: is group's mask. "o" :: is other's mask. "w" :: is write permission. "r" :: is read permission. "x" :: is execute permission. "X" :: is execute permission for directories only, must be used in conjunction with "+" "s" :: is uid, gid. "t" :: is sticky bit. "+" :: is added to a class given the specified mode. "-" :: Is removed from a given class given mode. "=" :: Is the exact nature of the class will be given a specified mode.
# File lib/epitools/path.rb, line 1202 def chmod(mode) FileUtils.chmod(mode, self) self end
# File lib/epitools/path.rb, line 1213 def chmod_R(mode) if directory? FileUtils.chmod_R(mode, self) self else raise "Not a directory." end end
# File lib/epitools/path.rb, line 1207 def chown(usergroup) user, group = usergroup.split(":") FileUtils.chown(user, group, self) self end
# File lib/epitools/path.rb, line 1222 def chown_R(usergroup) user, group = usergroup.split(":") if directory? FileUtils.chown_R(user, group, self) self else raise "Not a directory." end end
# File lib/epitools/path.rb, line 1160 def cp(dest) FileUtils.cp(path, dest) dest end
Copy a file to a destination, creating all intermediate directories if they don't already exist
# File lib/epitools/path.rb, line 1149 def cp_p(dest) FileUtils.mkdir_p(dest.dir) unless File.directory? dest.dir if file? FileUtils.cp(path, dest) elsif dir? FileUtils.cp_r(path, dest) end dest end
# File lib/epitools/path.rb, line 1141 def cp_r(dest) FileUtils.cp_r(path, dest) #if Path[dest].exists? dest end
# File lib/epitools/path.rb, line 397 def ctime lstat.ctime end
gzip the file, returning the result as a string
# File lib/epitools/path.rb, line 1285 def deflate(level=nil) Zlib.deflate(read, level) end
The current directory (with a trailing /)
# File lib/epitools/path.rb, line 322 def dir if dirs if relative? File.join(*dirs) else File.join("", *dirs) end else nil end end
# File lib/epitools/path.rb, line 238 def dir=(newdir) dirs = File.expand_path(newdir).split(File::SEPARATOR) dirs = dirs[1..-1] if dirs.size > 0 @dirs = dirs end
# File lib/epitools/path.rb, line 429 def dir? File.directory? path end
Read the contents of a file one chunk at a time (default chunk size is 16k)
# File lib/epitools/path.rb, line 659 def each_chunk(chunk_size=2**14) open do |io| yield io.read(chunk_size) until io.eof? end end
All the lines in this file, chomped.
# File lib/epitools/path.rb, line 669 def each_line return to_enum(:each_line) unless block_given? open { |io| io.each_line { |line| yield line.chomp } } end
# File lib/epitools/path.rb, line 1354 def endswith(s); path.endswith(s); end
# File lib/epitools/path.rb, line 416 def executable? mode & 0o111 > 0 end
fstat
# File lib/epitools/path.rb, line 368 def exists? File.exist? path end
TODO: Figure out how to fix the 'path.with(:ext=>ext+“.other”)' problem (when 'ext == nil')…
# File lib/epitools/path.rb, line 247 def ext=(newext) if newext.blank? @ext = nil return end newext = newext[1..-1] if newext.startswith('.') if newext['.'] self.filename = basename + '.' + newext else @ext = newext end end
# File lib/epitools/path.rb, line 350 def exts extensions = basename.split('.')[1..-1] extensions += [@ext] if @ext extensions end
# File lib/epitools/path.rb, line 433 def file? File.file? path end
# File lib/epitools/path.rb, line 334 def filename if base if ext base + "." + ext else base end else nil end end
# File lib/epitools/path.rb, line 220 def filename=(newfilename) if newfilename.nil? @ext, @base = nil, nil else ext = File.extname(newfilename) if ext.blank? @ext = nil @base = newfilename else self.ext = ext if pos = newfilename.rindex(ext) @base = newfilename[0...pos] end end end end
Yields all matching lines in the file (by returning an Enumerator
, or receiving a block)
# File lib/epitools/path.rb, line 682 def grep(pat) return to_enum(:grep, pat).to_a unless block_given? each_line do |line| yield line if line[pat] end end
Quickly gunzip a file, creating a new file, without removing the original, and returning a Path
to that new file.
# File lib/epitools/path.rb, line 1321 def gunzip! raise "Not a .gz file" unless ext == "gz" regular_file = self.with(:ext=>nil) regular_file.open("wb") do |output| Zlib::GzipReader.open(self) do |gzreader| IO.copy_stream(gzreader, output) end end update(regular_file) end
Quickly gzip a file, creating a new .gz file, without removing the original, and returning a Path
to that new file.
# File lib/epitools/path.rb, line 1303 def gzip!(level=nil) gz_file = self.with(:filename=>filename+".gz") raise "#{gz_file} already exists" if gz_file.exists? open("rb") do |input| Zlib::GzipWriter.open(gz_file) do |gzwriter| IO.copy_stream(input, gzwriter) end end update(gz_file) end
# File lib/epitools/path.rb, line 508 def hash; path.hash; end
Read ID3 tags (requires 'id3tag' gem)
Available fields:
tag.artist, tag.title, tag.album, tag.year, tag.track_nr, tag.genre, tag.get_frame(:TIT2)&.content, tag.get_frames(:COMM).first&.content, tag.get_frames(:COMM).last&.language
# File lib/epitools/path.rb, line 978 def id3 ID3Tag.read(io) end
gunzip the file, returning the result as a string
# File lib/epitools/path.rb, line 1294 def inflate Zlib.inflate(read) end
# File lib/epitools/path.rb, line 148 def initialize_copy(other) @dirs = other.dirs && other.dirs.dup @base = other.base && other.base.dup @ext = other.ext && other.ext.dup end
inspect
# File lib/epitools/path.rb, line 360 def inspect "#<Path:#{path}>" end
Path.join(“anything{}”).path == “/etc/anything{}” (globs ignored)
# File lib/epitools/path.rb, line 518 def join(other) Path.new File.join(self, other) end
# File lib/epitools/path.rb, line 1165 def ln_s(dest) if dest.startswith("/") Path.ln_s(self, dest) else Path.ln_s(self, self / dest) end end
Returns all the files in the directory that this path points to
# File lib/epitools/path.rb, line 697 def ls Dir.foreach(path). reject {|fn| fn == "." or fn == ".." }. flat_map {|fn| self / fn } end
Returns all the directories in this path
# File lib/epitools/path.rb, line 716 def ls_dirs ls.select(&:dir?) #Dir.glob("#{path}*/", File::FNM_DOTMATCH).map { |s| Path.new(s, :type=>:dir) } end
Returns all the files in this path
# File lib/epitools/path.rb, line 724 def ls_files ls.select(&:file?) #Dir.glob("#{path}*", File::FNM_DOTMATCH).map { |s| Path.new(s, :type=>:file) } end
Returns all files in this path's directory and its subdirectories
# File lib/epitools/path.rb, line 706 def ls_r(symlinks=false) # glob = symlinks ? "**{,/*/**}/*" : "**/*" # Path[File.join(path, glob)] Find.find(path).drop(1).map {|fn| Path.new(fn) } end
# File lib/epitools/path.rb, line 378 def lstat @lstat ||= File.lstat self # to cache, or not to cache? that is the question. # File.lstat self # ...answer: not to cache! end
Find the file's mimetype (by magic)
# File lib/epitools/path.rb, line 1389 def magic open { |io| MimeMagic.by_magic(io) } end
# File lib/epitools/path.rb, line 1270 def md5 Digest::MD5.file(self).hexdigest end
Find the file's mimetype (first from file extension, then by magic)
# File lib/epitools/path.rb, line 1374 def mimetype mimetype_from_ext || magic end
Find the file's mimetype (only using the file extension)
# File lib/epitools/path.rb, line 1382 def mimetype_from_ext MimeMagic.by_extension(ext) end
# File lib/epitools/path.rb, line 1137 def mkcd(&block) Path.mkcd(self, &block) end
# File lib/epitools/path.rb, line 383 def mode lstat.mode end
# File lib/epitools/path.rb, line 387 def mtime lstat.mtime end
# File lib/epitools/path.rb, line 391 def mtime=(new_mtime) File.utime(atime, new_mtime, path) @lstat = nil new_mtime end
Works the same as “rename”, but the destination can be on another disk.
# File lib/epitools/path.rb, line 1031 def mv(arg) dest = arg_to_path(arg) raise "Error: can't move #{self.inspect} because source location doesn't exist." unless exists? FileUtils.mv(path, dest) dest end
Moves the file (overwriting the destination if it already exists). Also points the current Path
object at the new destination.
# File lib/epitools/path.rb, line 1052 def mv!(arg) update(mv(arg)) end
# File lib/epitools/path.rb, line 346 def name filename || "#{dirs.last}/" end
Rename this file, “filename.ext”, to “filename (1).ext” (or (2), or (3), or whatever number is available.) (Does not modify this Path
object.)
# File lib/epitools/path.rb, line 1086 def numbered_backup! rename(numbered_backup_file) end
Find a backup filename that doesn't exist yet by appending “(1)”, “(2)”, etc. to the current filename.
# File lib/epitools/path.rb, line 1060 def numbered_backup_file return self unless exists? n = 1 loop do if dir? new_file = with(:dirs => dirs[0..-2] + ["#{dirs.last} (#{n})"]) else new_file = with(:basename => "#{basename} (#{n})") end return new_file unless new_file.exists? n += 1 end end
Open the file (default: read-only + binary mode)
# File lib/epitools/path.rb, line 639 def open(mode="rb", &block) if block_given? File.open(path, mode, &block) else File.open(path, mode) end end
FIXME: Does the current user own this file?
# File lib/epitools/path.rb, line 412 def owner? raise "STUB" end
Find the parent directory. If the `Path` is a filename, it returns the containing directory.
# File lib/epitools/path.rb, line 1345 def parent if file? with(:filename=>nil) else with(:dirs=>dirs[0...-1]) end end
# File lib/epitools/path.rb, line 468 def parent_of?(child) dirs == child.dirs[0...dirs.size] end
Parse the file based on the file extension. (Handles json, html, yaml, xml, csv, marshal, and bson.)
# File lib/epitools/path.rb, line 879 def parse(io=self.io, forced_ext=nil, opts={}) case (forced_ext or ext.downcase) when 'gz', 'bz2', 'xz' parse(zopen, exts[-2]) when 'json' read_json(io) when 'html', 'htm' read_html(io) when 'yaml', 'yml' read_yaml(io) when 'xml', 'rdf', 'rss' read_xml(io) when 'csv' read_csv(io, opts) when 'marshal' read_marshal(io) when 'bson' read_bson(io) else raise "Unrecognized format: #{ext}" end end
Treat each line of the file as a json object, and parse them all, returning an array of hashes
# File lib/epitools/path.rb, line 905 def parse_lines each_line.map { |line| JSON.parse line } end
Joins and returns the full path
# File lib/epitools/path.rb, line 291 def path if d = dir File.join(d, (filename || "") ) else "" end end
This is the core that initializes the whole class.
Note: The `hints` parameter contains options so `path=` doesn't have to touch the filesytem as much.
# File lib/epitools/path.rb, line 197 def path=(newpath, hints={}) if hints[:type] or File.exist? newpath if hints[:type] == :dir or File.directory? newpath self.dir = newpath else self.dir, self.filename = File.split(newpath) end else if newpath.endswith(File::SEPARATOR) # ends in '/' self.dir = newpath else self.dir, self.filename = File.split(newpath) end end # FIXME: Make this work with globs. if hints[:relative] update(relative_to(Path.pwd)) elsif hints[:relative_to] update(relative_to(hints[:relative_to])) end end
Append data, with a newline at the end
# File lib/epitools/path.rb, line 773 def puts(data=nil) append data append "\n" unless data and data[-1] == "\n" end
Read bytes from the file (just a wrapper around File.read)
# File lib/epitools/path.rb, line 652 def read(length=nil, offset=nil) File.read(path, length, offset) end
Parse the file as BSON
# File lib/epitools/path.rb, line 962 def read_bson(io=self.io) BSON.deserialize(read) end
Parse the file as CSV
# File lib/epitools/path.rb, line 941 def read_csv(io=self.io, opts={}) CSV.new(io.read, **opts).each end
# File lib/epitools/path.rb, line 922 def read_html(io=self.io) Nokogiri::HTML(io) end
Parse the file as JSON
# File lib/epitools/path.rb, line 911 def read_json(io=self.io) JSON.load(io) end
Parse the file as a Ruby Marshal dump
# File lib/epitools/path.rb, line 952 def read_marshal(io=self.io) Marshal.load(io) end
Parse the file as XML
# File lib/epitools/path.rb, line 947 def read_xml(io=self.io) Nokogiri::XML(io) end
Parse the file as YAML
# File lib/epitools/path.rb, line 934 def read_yaml(io=self.io) YAML.load(io) end
# File lib/epitools/path.rb, line 425 def readable? mode & 0o444 > 0 end
# File lib/epitools/path.rb, line 1360 def realpath Path.new File.realpath(path) end
Is this a relative path?
# File lib/epitools/path.rb, line 302 def relative? # FIXME: Need a Path::Relative subclass, so that "dir/filename" can be valid. # (If the user changes dirs, the relative path should change too.) dirs.first == ".." end
# File lib/epitools/path.rb, line 315 def relative_to(anchor) anchor = anchor.to_s anchor += "/" unless anchor[/\/$/] to_s.gsub(/^#{Regexp.escape(anchor)}/, '') end
Reload this path (updates cached values.)
# File lib/epitools/path.rb, line 273 def reload! temp = path reset! self.path = temp @attrs = nil self end
Renames the file, but doesn't change the current Path
object, and returns a Path
that points at the new filename.
Examples:
Path["file"].rename("newfile") #=> Path["newfile"] Path["SongySong.mp3"].rename(:basename=>"Songy Song") Path["Songy Song.mp3"].rename(:ext=>"aac") Path["Songy Song.aac"].rename(:dir=>"/music2") Path["/music2/Songy Song.aac"].exists? #=> true
# File lib/epitools/path.rb, line 1017 def rename(arg) dest = arg_to_path(arg) raise "Error: destination (#{dest.inspect}) already exists" if dest.exists? raise "Error: can't rename #{self.inspect} because source location doesn't exist." unless exists? File.rename(path, dest) dest end
Rename the file and change this Path
object so that it points to the destination file.
# File lib/epitools/path.rb, line 1044 def rename!(arg) update(rename(arg)) end
Clear out the internal state of this object, so that it can be reinitialized.
# File lib/epitools/path.rb, line 265 def reset! [:@dirs, :@base, :@ext].each { |var| remove_instance_variable(var) rescue nil } self end
Remove a file or directory
# File lib/epitools/path.rb, line 1237 def rm raise "Error: #{self} does not exist" unless symlink? or exists? if directory? and not symlink? Dir.rmdir(self) == 0 else File.unlink(self) == 1 end end
Checksums
# File lib/epitools/path.rb, line 1260 def sha1 Digest::SHA1.file(self).hexdigest end
# File lib/epitools/path.rb, line 1265 def sha2 Digest::SHA2.file(self).hexdigest end
# File lib/epitools/path.rb, line 1275 def sha256 Digest::SHA256.file(self).hexdigest end
Returns all neighbouring directories to this path
# File lib/epitools/path.rb, line 732 def siblings Path[dir].ls - [self] end
# File lib/epitools/path.rb, line 372 def size File.size(path) rescue Errno::ENOENT -1 end
An array of attributes which will be used sort paths (case insensitive, directories come first)
# File lib/epitools/path.rb, line 488 def sort_attrs [(filename ? 1 : 0), path.downcase] end
# File lib/epitools/path.rb, line 1353 def startswith(s); path.startswith(s); end
# File lib/epitools/path.rb, line 437 def symlink? File.symlink? path end
# File lib/epitools/path.rb, line 445 def symlink_target target = File.readlink(path.gsub(/\/$/, '')) if target.startswith("/") Path[target] else Path[dir] / target end end
No-op (returns self)
# File lib/epitools/path.rb, line 1614 def to_Path self end
Like the unix `touch` command (if the file exists, update its timestamp, otherwise create a new file)
# File lib/epitools/path.rb, line 740 def touch open("a") { } self end
Shrink or expand the size of a file in-place
# File lib/epitools/path.rb, line 1253 def truncate(offset=0) File.truncate(self, offset) if exists? end
Returns the filetype (as a standard file extension), verified with Magic.
(In other words, this will give you the true extension, even if the file's extension is wrong.)
Note: Prefers long extensions (eg: jpeg over jpg)
TODO: rename type => magicext?
# File lib/epitools/path.rb, line 1403 def type @cached_type ||= begin if file? or symlink? ext = self.ext magic = self.magic if ext and magic if magic.extensions.include? ext ext else magic.ext # in case the supplied extension is wrong... end elsif !ext and magic magic.ext elsif ext and !magic ext else # !ext and !magic :unknown end elsif dir? :directory end end end
# File lib/epitools/path.rb, line 690 def unmarshal read.unmarshal end
# File lib/epitools/path.rb, line 282 def update(other) @dirs = other.dirs @base = other.base @ext = other.ext end
# File lib/epitools/path.rb, line 461 def uri?; false; end
# File lib/epitools/path.rb, line 462 def url?; uri?; end
# File lib/epitools/path.rb, line 421 def writable? mode & 0o222 > 0 end
Overwrite the data in this file (accepts a string, an IO
, or it can yield the file handle to a block.)
# File lib/epitools/path.rb, line 781 def write(data=nil) self.open("wb") do |f| if data and not block_given? if data.is_an? IO IO.copy_stream(data, f) else f.write(data) end else yield f end end end
Serilize an object to BSON format and write it to this path
# File lib/epitools/path.rb, line 967 def write_bson(object) write BSON.serialize(object) end
Convert the object to JSON and write it to the file (overwriting the existing file).
# File lib/epitools/path.rb, line 917 def write_json(object) write object.to_json end
Serilize an object to Ruby Marshal format and write it to this path
# File lib/epitools/path.rb, line 957 def write_marshal(object) write object.marshal end
Convert the object to YAML and write it to the file (overwriting the existing file).
# File lib/epitools/path.rb, line 929 def write_yaml(object) write object.to_yaml end
A mutation of “open” that lets you read/write gzip files, as well as regular files.
(NOTE: gzip detection is based on the filename, not the contents.)
It
accepts a block just like open()!
Example:
zopen("test.txt") #=> #<File:test.txt> zopen("test.txt.gz") #=> #<Zlib::GzipReader:0xb6c79424> zopen("otherfile.gz", "w") #=> #<Zlib::GzipWriter:0x7fe30448>> zopen("test.txt.gz") { |f| f.read } # read the contents of the .gz file, then close the file handle automatically.
# File lib/epitools/path.rb, line 819 def zopen(mode="rb", &block) # if ext == "gz" # io = open(mode) # case mode # when "r", "rb" # io = Zlib::GzipReader.new(io) # def io.to_str; read; end # when "w", "wb" # io = Zlib::GzipWriter.new(io) # else # raise "Unknown mode: #{mode.inspect}. zopen only supports 'r' and 'w'." # end # elsif bin = COMPRESSORS[ext] if bin = COMPRESSORS[ext] if which(bin) case mode when "w", "wb" # TODO: figure out how to pipe the compressor directly a file so we don't require a block raise "Error: Must supply a block when writing" unless block_given? IO.popen([bin, "-c"], "wb+") do |compressor| yield(compressor) compressor.close_write open("wb") { |output| IO.copy_stream(compressor, output) } end when "r", "rb" if block_given? IO.popen([bin, "-d" ,"-c", path], "rb", &block) else IO.popen([bin, "-d" ,"-c", path], "rb") end else raise "Error: Mode #{mode.inspect} not recognized" end else raise "Error: couldn't find #{bin.inspect} in the path" end else # io = open(path) raise "Error: #{ext.inspect} is an unsupported format" end # if block_given? # result = yield(io) # io.close # result # else # io # end end
Private Instance Methods
A private method for handling arguments to mv and rename.
# File lib/epitools/path.rb, line 995 def arg_to_path(arg) case arg when String, Path Path[arg] when Hash self.with(arg) else raise "Error: argument must be a path (a String or a Path), or a hash of attributes to replace in the Path." end end