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

base[RW]

The filename without an extension

basename[RW]

The filename without an extension

basename=[RW]

The filename without an extension

dirs[RW]

The directories in the path, split into an array. (eg: ['usr', 'src', 'linux'])

ext[R]

The file extension, including the . (eg: “.mp3”)

extension[R]

The file extension, including the . (eg: “.mp3”)

extname[R]

The file extension, including the . (eg: “.mp3”)

Public Class Methods

[](path) click to toggle source
# 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
cd(dest) { |dest| ... } click to toggle source

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
escape(str) click to toggle source
# File lib/epitools/path.rb, line 154
def self.escape(str)
  Shellwords.escape(str)
end
expand_path(orig_path) click to toggle source

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
getfattr(path) click to toggle source

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
glob(str, hints={}) click to toggle source
# File lib/epitools/path.rb, line 158
def self.glob(str, hints={})
  Dir[str].map { |entry| new(entry, hints) }
end
home() click to toggle source

User's current home directory

# File lib/epitools/path.rb, line 1526
def self.home
  Path[ENV['HOME']]
end
ln_s(src, dest) click to toggle source
# File lib/epitools/path.rb, line 1575
def self.ln_s(src, dest)
  FileUtils.ln_s(src, dest)
  Path[dest]
end
ls(path) click to toggle source
# File lib/epitools/path.rb, line 1571
def self.ls(path); Path[path].ls  end
ls_r(path) click to toggle source
# File lib/epitools/path.rb, line 1573
def self.ls_r(path); Path[path].ls_r; end
mkcd(path, &block) click to toggle source

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
new(*args) click to toggle source

Initializers

Calls superclass method
# 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
new(newpath, hints={}) click to toggle source
# 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
popd() click to toggle source
# File lib/epitools/path.rb, line 1542
def self.popd
  @@dir_stack ||= [pwd]
  @@dir_stack.pop
end
pushd(destination) click to toggle source
# File lib/epitools/path.rb, line 1537
def self.pushd(destination)
  @@dir_stack ||= []
  @@dir_stack.push pwd
end
pwd() click to toggle source

The current directory

# File lib/epitools/path.rb, line 1533
def self.pwd
  Path.new expand_path(Dir.pwd)
end
setfattr(path, key, value) click to toggle source

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
tmpdir(prefix="tmp") click to toggle source

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
tmpfile(prefix="tmp") { |path| ... } click to toggle source

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
which(bin, *extras) click to toggle source

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

/(other) click to toggle source

Path/“passwd” == Path (globs permitted)

# File lib/epitools/path.rb, line 526
def /(other)
  # / <- fixes jedit syntax highlighting bug.
  # TODO: make it work for "/dir/dir"/"/dir/file"
  #Path.new( File.join(self, other) )
  Path[ File.join(self, other) ]
end
<<(data=nil)
Alias for: append
<=>(other) click to toggle source
# 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
==(other) click to toggle source
# File lib/epitools/path.rb, line 503
def ==(other)
  self.path == other.to_s
end
Also aliased as: eql?
=~(pattern) click to toggle source

Match the full path against a regular expression

# File lib/epitools/path.rb, line 1338
def =~(pattern)
  to_s =~ pattern
end
[](key) click to toggle source

Retrieve one of this file's xattrs

# File lib/epitools/path.rb, line 620
def [](key)
  attrs[key]
end
[]=(key, value) click to toggle source

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=nil) { |f| ... } click to toggle source

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
Also aliased as: <<
atime() click to toggle source
# File lib/epitools/path.rb, line 401
def atime
  lstat.atime
end
atime=(new_atime) click to toggle source
# File lib/epitools/path.rb, line 405
def atime=(new_atime)
  File.utime(new_atime, mtime, path)
  @lstat = nil
  new_atime
end
attrs() click to toggle source

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
Also aliased as: xattrs
attrs=(new_attrs) click to toggle source

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
backup!() click to toggle source

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
backup_file() click to toggle source

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
cd(&block) click to toggle source

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
child_of?(parent) click to toggle source
# File lib/epitools/path.rb, line 464
def child_of?(parent)
  parent.parent_of? self
end
chmod(mode) click to toggle source

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
chmod_R(mode) click to toggle source
# 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
chown(usergroup) click to toggle source
# File lib/epitools/path.rb, line 1207
def chown(usergroup)
  user, group = usergroup.split(":")
  FileUtils.chown(user, group, self)
  self
end
chown_R(usergroup) click to toggle source
# 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
cp(dest) click to toggle source
# File lib/epitools/path.rb, line 1160
def cp(dest)
  FileUtils.cp(path, dest)
  dest
end
cp_p(dest) click to toggle source

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
cp_r(dest) click to toggle source
# File lib/epitools/path.rb, line 1141
def cp_r(dest)
  FileUtils.cp_r(path, dest) #if Path[dest].exists?
  dest
end
ctime() click to toggle source
# File lib/epitools/path.rb, line 397
def ctime
  lstat.ctime
end
deflate(level=nil) click to toggle source

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
Also aliased as: gzip
delete!()
Alias for: rm
dir() click to toggle source

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
Also aliased as: dirname, directory
dir=(newdir) click to toggle source
# 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
Also aliased as: dirname=, directory=
dir?() click to toggle source
# File lib/epitools/path.rb, line 429
def dir?
  File.directory? path
end
Also aliased as: directory?
directory()
Alias for: dir
directory=(newdir)
Alias for: dir=
directory?()
Alias for: dir?
dirname()
Alias for: dir
dirname=(newdir)
Alias for: dir=
each()
Alias for: each_line
each_chunk(chunk_size=2**14) { |readuntil eof?| ... } click to toggle source

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
each_line() { |chomp| ... } click to toggle source

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
Also aliased as: each, lines, nicelines, nice_lines
endswith(s) click to toggle source
# File lib/epitools/path.rb, line 1354
def endswith(s); path.endswith(s); end
eql?(other)
Alias for: ==
exe?()
Alias for: executable?
executable?() click to toggle source
# File lib/epitools/path.rb, line 416
def executable?
  mode & 0o111 > 0
end
Also aliased as: exe?
exist?()
Alias for: exists?
exists?() click to toggle source

fstat

# File lib/epitools/path.rb, line 368
def exists?
  File.exist? path
end
Also aliased as: exist?
ext=(newext) click to toggle source

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
Also aliased as: extname=, extension=
extension=(newext)
Alias for: ext=
extname=(newext)
Alias for: ext=
exts() click to toggle source
# File lib/epitools/path.rb, line 350
def exts
  extensions = basename.split('.')[1..-1]
  extensions += [@ext] if @ext
  extensions
end
file?() click to toggle source
# File lib/epitools/path.rb, line 433
def file?
  File.file? path
end
filename() click to toggle source
# File lib/epitools/path.rb, line 334
def filename
  if base
    if ext
      base + "." + ext
    else
      base
    end
  else
    nil
  end
end
filename=(newfilename) click to toggle source
# 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
from_csv(io=self.io, opts={})
Alias for: read_csv
from_html(io=self.io)
Alias for: read_html
from_json(io=self.io)
Alias for: read_json
from_yaml(io=self.io)
Alias for: read_yaml
grep(pat) { |line| ... } click to toggle source

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
gunzip()
Alias for: inflate
gunzip!() click to toggle source

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
gzip(level=nil)
Alias for: deflate
gzip!(level=nil) click to toggle source

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
hash() click to toggle source
# File lib/epitools/path.rb, line 508
def hash; path.hash; end
hidden?() click to toggle source
# File lib/epitools/path.rb, line 456
def hidden?
  thing = filename ? filename : dirs.last
  !!thing[/^\../]
end
id3() click to toggle source

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
Also aliased as: id3tags
id3tags()
Alias for: id3
identify()
Alias for: mimetype
inflate() click to toggle source

gunzip the file, returning the result as a string

# File lib/epitools/path.rb, line 1294
def inflate
  Zlib.inflate(read)
end
Also aliased as: gunzip
initialize_copy(other) click to toggle source
# 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() click to toggle source

inspect

# File lib/epitools/path.rb, line 360
def inspect
  "#<Path:#{path}>"
end
io(mode="rb", &block)
Alias for: open
join(other) click to toggle source

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
lines()
Alias for: each_line
ln_s(dest) click to toggle source
# 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
Also aliased as: symlink_to
ls() click to toggle source

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
ls_R(symlinks=false)
Alias for: ls_r
ls_dirs() click to toggle source

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
ls_files() click to toggle source

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
ls_r(symlinks=false) click to toggle source

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
Also aliased as: ls_R
lstat() click to toggle source
# 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
magic() click to toggle source

Find the file's mimetype (by magic)

# File lib/epitools/path.rb, line 1389
def magic
  open { |io| MimeMagic.by_magic(io) }
end
md5() click to toggle source
# File lib/epitools/path.rb, line 1270
def md5
  Digest::MD5.file(self).hexdigest
end
Also aliased as: md5sum
md5sum()
Alias for: md5
mimetype() click to toggle source

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
Also aliased as: identify
mimetype_from_ext() click to toggle source

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
mkcd(&block) click to toggle source

Path.mkcd(self)

# File lib/epitools/path.rb, line 1137
def mkcd(&block)
  Path.mkcd(self, &block)
end
mode() click to toggle source
# File lib/epitools/path.rb, line 383
def mode
  lstat.mode
end
move(arg)
Alias for: mv
move!(arg)
Alias for: mv!
mtime() click to toggle source
# File lib/epitools/path.rb, line 387
def mtime
  lstat.mtime
end
mtime=(new_mtime) click to toggle source
# File lib/epitools/path.rb, line 391
def mtime=(new_mtime)
  File.utime(atime, new_mtime, path)
  @lstat = nil
  new_mtime
end
mv(arg) click to toggle source

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
Also aliased as: move
mv!(arg) click to toggle source

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
Also aliased as: move!
name() click to toggle source
# File lib/epitools/path.rb, line 346
def name
  filename || "#{dirs.last}/"
end
nice_lines()
Alias for: each_line
nicelines()
Alias for: each_line
numbered_backup!() click to toggle source

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
numbered_backup_file() click to toggle source

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(mode="rb", &block) click to toggle source

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
Also aliased as: io, stream
owner?() click to toggle source

FIXME: Does the current user own this file?

# File lib/epitools/path.rb, line 412
def owner?
  raise "STUB"
end
parent() click to toggle source

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
parent_of?(child) click to toggle source
# File lib/epitools/path.rb, line 468
def parent_of?(child)
  dirs == child.dirs[0...dirs.size]
end
parse(io=self.io, forced_ext=nil, opts={}) click to toggle source

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
parse_lines() click to toggle source

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
path() click to toggle source

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
Also aliased as: to_path, to_str, to_s, pathname
path=(newpath, hints={}) click to toggle source

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
pathname()
Alias for: path
puts(data=nil) click to toggle source

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(length=nil, offset=nil) click to toggle source

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
read_bson(io=self.io) click to toggle source

Parse the file as BSON

# File lib/epitools/path.rb, line 962
def read_bson(io=self.io)
  BSON.deserialize(read)
end
read_csv(io=self.io, opts={}) click to toggle source

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
Also aliased as: from_csv
read_html(io=self.io) click to toggle source
# File lib/epitools/path.rb, line 922
def read_html(io=self.io)
  Nokogiri::HTML(io)
end
Also aliased as: from_html
read_json(io=self.io) click to toggle source

Parse the file as JSON

# File lib/epitools/path.rb, line 911
def read_json(io=self.io)
  JSON.load(io)
end
Also aliased as: from_json
read_marshal(io=self.io) click to toggle source

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
read_xml(io=self.io) click to toggle source

Parse the file as XML

# File lib/epitools/path.rb, line 947
def read_xml(io=self.io)
  Nokogiri::XML(io)
end
read_yaml(io=self.io) click to toggle source

Parse the file as YAML

# File lib/epitools/path.rb, line 934
def read_yaml(io=self.io)
  YAML.load(io)
end
Also aliased as: from_yaml
readable?() click to toggle source
# File lib/epitools/path.rb, line 425
def readable?
  mode & 0o444 > 0
end
realpath() click to toggle source
# File lib/epitools/path.rb, line 1360
def realpath
  Path.new File.realpath(path)
end
relative() click to toggle source

Path relative to current directory (Path.pwd)

# File lib/epitools/path.rb, line 311
def relative
  relative_to(pwd)
end
relative?() click to toggle source

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
relative_to(anchor) click to toggle source
# 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!() click to toggle source

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
remove!()
Alias for: rm
ren(arg)
Alias for: rename
ren!(arg)
Alias for: rename!
rename(arg) click to toggle source

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
Also aliased as: ren
rename!(arg) click to toggle source

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
Also aliased as: ren!
reset!() click to toggle source

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
rm() click to toggle source

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
Also aliased as: delete!, unlink!, remove!
sha1() click to toggle source

Checksums

# File lib/epitools/path.rb, line 1260
def sha1
  Digest::SHA1.file(self).hexdigest
end
Also aliased as: sha1sum
sha1sum()
Alias for: sha1
sha2() click to toggle source
# File lib/epitools/path.rb, line 1265
def sha2
  Digest::SHA2.file(self).hexdigest
end
Also aliased as: sha2sum
sha256() click to toggle source
# File lib/epitools/path.rb, line 1275
def sha256
  Digest::SHA256.file(self).hexdigest
end
Also aliased as: sha256sum
sha256sum()
Alias for: sha256
sha2sum()
Alias for: sha2
siblings() click to toggle source

Returns all neighbouring directories to this path

# File lib/epitools/path.rb, line 732
def siblings
  Path[dir].ls - [self]
end
size() click to toggle source
# File lib/epitools/path.rb, line 372
def size
  File.size(path)
rescue Errno::ENOENT
  -1
end
sort_attrs() click to toggle source

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
startswith(s) click to toggle source
# File lib/epitools/path.rb, line 1353
def startswith(s); path.startswith(s); end
stream(mode="rb", &block)
Alias for: open
target()
Alias for: symlink_target
to_Path() click to toggle source

No-op (returns self)

# File lib/epitools/path.rb, line 1614
def to_Path
  self
end
to_path()

aliases

Alias for: path
to_s()
Alias for: path
to_str()
Alias for: path
touch() click to toggle source

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
truncate(offset=0) click to toggle source

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
type() click to toggle source

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
unmarshal() click to toggle source
# File lib/epitools/path.rb, line 690
def unmarshal
  read.unmarshal
end
update(other) click to toggle source
# File lib/epitools/path.rb, line 282
def update(other)
  @dirs = other.dirs
  @base = other.base
  @ext  = other.ext
end
uri?() click to toggle source
# File lib/epitools/path.rb, line 461
def uri?; false; end
url?() click to toggle source
# File lib/epitools/path.rb, line 462
def url?; uri?; end
writable?() click to toggle source
# File lib/epitools/path.rb, line 421
def writable?
  mode & 0o222 > 0
end
write(data=nil) { |f| ... } click to toggle source

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
write_bson(object) click to toggle source

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
write_json(object) click to toggle source

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
write_marshal(object) click to toggle source

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
write_yaml(object) click to toggle source

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
xattrs()
Alias for: attrs
zopen(mode="rb") { |compressor| ... } click to toggle source

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

arg_to_path(arg) click to toggle source

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