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 163 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 1567 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 155 def self.escape(str) Shellwords.escape(str) end
Same as File.expand_path, except preserves the trailing ‘/’.
# File lib/epitools/path.rb, line 1509 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 541 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 159 def self.glob(str, **hints) Dir[str].map { |entry| new(entry, **hints) } end
User’s current home directory
# File lib/epitools/path.rb, line 1541 def self.home Path[ENV['HOME']] end
# File lib/epitools/path.rb, line 1590 def self.ln_s(src, dest) FileUtils.ln_s(src, dest) Path[dest] end
# File lib/epitools/path.rb, line 1586 def self.ls(path); Path[path].ls end
# File lib/epitools/path.rb, line 1588 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 1139 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
# File lib/epitools/path.rb, line 129 def self.new(*args) if args.first =~ URI_RE and self != Path::URI Path::URI.new(args.first) else old_new(*args) end end
# File lib/epitools/path.rb, line 137 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 1557 def self.popd @@dir_stack ||= [pwd] @@dir_stack.pop end
# File lib/epitools/path.rb, line 1552 def self.pushd(destination) @@dir_stack ||= [] @@dir_stack.push pwd end
The current directory
# File lib/epitools/path.rb, line 1548 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 569 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 1531 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 1518 def self.tmpfile(prefix="tmp") # path = Path.new(Tempfile.new(prefix).path, unlink_when_garbage_collected: true) path = Path.new(Tempfile.new(prefix).path) 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 1614 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
# File lib/epitools/path.rb, line 872 def self.zopen(filename, mode, &block) Path.new(filename).zopen(mode, &block) end
Public Instance Methods
# File lib/epitools/path.rb, line 493 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 504 def ==(other) self.path == other.to_s end
Match the full path against a regular expression
# File lib/epitools/path.rb, line 1352 def =~(pattern) to_s =~ pattern end
Retrieve one of this file’s xattrs
# File lib/epitools/path.rb, line 621 def [](key) attrs[key] end
Set this file’s xattr
# File lib/epitools/path.rb, line 628 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 754 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 402 def atime lstat.atime end
# File lib/epitools/path.rb, line 406 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 593 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 601 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 1108 def backup! rename(backup_file) end
Return a copy of this Path
with “.bak” at the end
# File lib/epitools/path.rb, line 1092 def backup_file with(:filename => filename+".bak") end
# File lib/epitools/path.rb, line 442 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 1001 def cd(&block) Path.cd(path, &block) end
# File lib/epitools/path.rb, line 465 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 1216 def chmod(mode) FileUtils.chmod(mode, self) self end
# File lib/epitools/path.rb, line 1227 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 1221 def chown(usergroup) user, group = usergroup.split(":") FileUtils.chown(user, group, self) self end
# File lib/epitools/path.rb, line 1236 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 1174 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 1163 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 1155 def cp_r(dest) FileUtils.cp_r(path, dest) #if Path[dest].exists? dest end
# File lib/epitools/path.rb, line 398 def ctime lstat.ctime end
gzip the file, returning the result as a string
# File lib/epitools/path.rb, line 1299 def deflate(level=nil) Zlib.deflate(read, level) end
The current directory (with a trailing /)
# File lib/epitools/path.rb, line 323 def dir if dirs if relative? File.join(*dirs) else File.join("", *dirs) end else nil end end
# File lib/epitools/path.rb, line 239 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 430 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 660 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 670 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 1368 def endswith(s); path.endswith(s); end
# File lib/epitools/path.rb, line 417 def executable? mode & 0o111 > 0 end
fstat
# File lib/epitools/path.rb, line 369 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 248 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 351 def exts extensions = basename.split('.')[1..-1] extensions += [@ext] if @ext extensions end
# File lib/epitools/path.rb, line 434 def file? File.file? path end
# File lib/epitools/path.rb, line 335 def filename if base if ext base + "." + ext else base end else nil end end
# File lib/epitools/path.rb, line 221 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 683 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 1335 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 1317 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 509 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 991 def id3 ID3Tag.read(io) end
gunzip the file, returning the result as a string
# File lib/epitools/path.rb, line 1308 def inflate Zlib.inflate(read) end
# File lib/epitools/path.rb, line 149 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 361 def inspect "#<Path:#{path}>" end
Path.join(“anything{}”).path == “/etc/anything{}” (globs ignored)
# File lib/epitools/path.rb, line 519 def join(other) Path.new File.join(self, other) end
# File lib/epitools/path.rb, line 1179 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 698 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 717 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 725 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 707 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 379 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 1403 def magic open { |io| MimeMagic.by_magic(io) } end
# File lib/epitools/path.rb, line 1284 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 1388 def mimetype mimetype_from_ext || magic end
Find the file’s mimetype (only using the file extension)
# File lib/epitools/path.rb, line 1396 def mimetype_from_ext MimeMagic.by_extension(ext) end
# File lib/epitools/path.rb, line 1151 def mkcd(&block) Path.mkcd(self, &block) end
# File lib/epitools/path.rb, line 384 def mode lstat.mode end
# File lib/epitools/path.rb, line 388 def mtime lstat.mtime end
# File lib/epitools/path.rb, line 392 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 1045 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 1066 def mv!(arg) update(mv(arg)) end
# File lib/epitools/path.rb, line 347 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 1100 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 1074 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 640 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 413 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 1359 def parent if file? with(:filename=>nil) else with(:dirs=>dirs[0...-1]) end end
# File lib/epitools/path.rb, line 469 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, tsv, marshal, and bson.)
The “format” option lets you specify the file format (eg: ‘Path.parse(format: “yaml”)`) You can also pass CSV parsing options (eg: `Path.parse(col_sep: “t”)`)
# File lib/epitools/path.rb, line 887 def parse(io=self.io, **opts) case (opts[:format] || ext.downcase) when 'gz', 'bz2', 'xz' parse(zopen, format: 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 'tsv' opts[:col_sep] ||= "\t" 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 916 def parse_lines each_line.map { |line| JSON.parse line } end
Joins and returns the full path
# File lib/epitools/path.rb, line 292 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 198 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 774 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 653 def read(length=nil, offset=nil) File.read(path, length, offset) end
Parse the file as BSON
# File lib/epitools/path.rb, line 975 def read_bson(io=self.io) BSON.deserialize(read) end
Parse the file as CSV
# File lib/epitools/path.rb, line 953 def read_csv(io=self.io, **opts) CSV.new(io.read, **opts).each end
# File lib/epitools/path.rb, line 933 def read_html(io=self.io) #Nokogiri::HTML(io) Oga.parse_html(io) end
Parse the file as JSON
# File lib/epitools/path.rb, line 922 def read_json(io=self.io) JSON.load(io) end
Parse the file as a Ruby Marshal dump
# File lib/epitools/path.rb, line 965 def read_marshal(io=self.io) Marshal.load(io) end
Parse the file as XML
# File lib/epitools/path.rb, line 959 def read_xml(io=self.io) # Nokogiri::XML(io) Oga.parse_xml(io) end
Parse the file as YAML
# File lib/epitools/path.rb, line 946 def read_yaml(io=self.io) YAML.load(io) end
# File lib/epitools/path.rb, line 426 def readable? mode & 0o444 > 0 end
# File lib/epitools/path.rb, line 1374 def realpath Path.new File.realpath(path) end
Is this a relative path?
# File lib/epitools/path.rb, line 303 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 316 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 274 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 1030 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 1058 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 266 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 1251 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 1274 def sha1 Digest::SHA1.file(self).hexdigest end
# File lib/epitools/path.rb, line 1279 def sha2 Digest::SHA2.file(self).hexdigest end
# File lib/epitools/path.rb, line 1289 def sha256 Digest::SHA256.file(self).hexdigest end
Returns all neighbouring directories to this path
# File lib/epitools/path.rb, line 733 def siblings Path[dir].ls - [self] end
# File lib/epitools/path.rb, line 373 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 489 def sort_attrs [(filename ? 1 : 0), path.downcase] end
# File lib/epitools/path.rb, line 1367 def startswith(s); path.startswith(s); end
# File lib/epitools/path.rb, line 438 def symlink? File.symlink? path end
# File lib/epitools/path.rb, line 446 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 1629 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 741 def touch open("a") { } self end
Shrink or expand the size of a file in-place
# File lib/epitools/path.rb, line 1267 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 1417 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 691 def unmarshal read.unmarshal end
# File lib/epitools/path.rb, line 283 def update(other) @dirs = other.dirs @base = other.base @ext = other.ext end
# File lib/epitools/path.rb, line 462 def uri?; false; end
# File lib/epitools/path.rb, line 463 def url?; uri?; end
# File lib/epitools/path.rb, line 422 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 782 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 980 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 928 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 970 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 941 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 820 def zopen(mode="rb", **opts, &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 = (opts[:format] || 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 1008 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