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 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
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 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
escape(str) click to toggle source
# File lib/epitools/path.rb, line 155
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 1509
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 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
glob(str, **hints) click to toggle source
# File lib/epitools/path.rb, line 159
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 1541
def self.home
  Path[ENV['HOME']]
end
ln_s(src, dest) click to toggle source
# File lib/epitools/path.rb, line 1590
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 1586
def self.ls(path); Path[path].ls  end
ls_r(path) click to toggle source
# File lib/epitools/path.rb, line 1588
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 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
new(*args) click to toggle source
# 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
new(newpath, **hints) click to toggle source
# 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
popd() click to toggle source
# File lib/epitools/path.rb, line 1557
def self.popd
  @@dir_stack ||= [pwd]
  @@dir_stack.pop
end
pushd(destination) click to toggle source
# File lib/epitools/path.rb, line 1552
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 1548
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 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
tmpdir(prefix="tmp") click to toggle source

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
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 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
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 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
zopen(filename, mode, &block) click to toggle source
# File lib/epitools/path.rb, line 872
def self.zopen(filename, mode, &block)
  Path.new(filename).zopen(mode, &block)
end

Public Instance Methods

/(other) click to toggle source

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

# File lib/epitools/path.rb, line 527
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 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
==(other) click to toggle source
# File lib/epitools/path.rb, line 504
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 1352
def =~(pattern)
  to_s =~ pattern
end
[](key) click to toggle source

Retrieve one of this file’s xattrs

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

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=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 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
Also aliased as: <<
atime() click to toggle source
# File lib/epitools/path.rb, line 402
def atime
  lstat.atime
end
atime=(new_atime) click to toggle source
# File lib/epitools/path.rb, line 406
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 593
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 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
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 1108
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 1092
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 1001
def cd(&block)
  Path.cd(path, &block)
end
child_of?(parent) click to toggle source
# File lib/epitools/path.rb, line 465
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 1216
def chmod(mode)
  FileUtils.chmod(mode, self)
  self
end
chmod_R(mode) click to toggle source
# 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
chown(usergroup) click to toggle source
# File lib/epitools/path.rb, line 1221
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 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
cp(dest) click to toggle source
# File lib/epitools/path.rb, line 1174
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 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
cp_r(dest) click to toggle source
# File lib/epitools/path.rb, line 1155
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 398
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 1299
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 323
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 239
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 430
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 660
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 670
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 1368
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 417
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 369
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 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
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 351
def exts
  extensions = basename.split('.')[1..-1]
  extensions += [@ext] if @ext
  extensions
end
file?() click to toggle source
# File lib/epitools/path.rb, line 434
def file?
  File.file? path
end
filename() click to toggle source
# File lib/epitools/path.rb, line 335
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 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
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 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
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 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
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 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
hash() click to toggle source
# File lib/epitools/path.rb, line 509
def hash; path.hash; end
hidden?() click to toggle source
# File lib/epitools/path.rb, line 457
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 991
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 1308
def inflate
  Zlib.inflate(read)
end
Also aliased as: gunzip
initialize_copy(other) click to toggle source
# 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() click to toggle source

inspect

# File lib/epitools/path.rb, line 361
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 519
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 1179
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 698
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 717
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 725
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 707
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 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
magic() click to toggle source

Find the file’s mimetype (by magic)

# File lib/epitools/path.rb, line 1403
def magic
  open { |io| MimeMagic.by_magic(io) }
end
md5() click to toggle source
# File lib/epitools/path.rb, line 1284
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 1388
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 1396
def mimetype_from_ext
  MimeMagic.by_extension(ext)
end
mkcd(&block) click to toggle source

Path.mkcd(self)

# File lib/epitools/path.rb, line 1151
def mkcd(&block)
  Path.mkcd(self, &block)
end
mode() click to toggle source
# File lib/epitools/path.rb, line 384
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 388
def mtime
  lstat.mtime
end
mtime=(new_mtime) click to toggle source
# File lib/epitools/path.rb, line 392
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 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
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 1066
def mv!(arg)
  update(mv(arg))
end
Also aliased as: move!
name() click to toggle source
# File lib/epitools/path.rb, line 347
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 1100
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 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(mode="rb", &block) click to toggle source

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

FIXME: Does the current user own this file?

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

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
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 916
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 292
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 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
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 774
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 653
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 975
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 953
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 933
def read_html(io=self.io)
  #Nokogiri::HTML(io)
  Oga.parse_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 922
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 965
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 959
def read_xml(io=self.io)
  # Nokogiri::XML(io)
  Oga.parse_xml(io)
end
read_yaml(io=self.io) click to toggle source

Parse the file as YAML

# File lib/epitools/path.rb, line 946
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 426
def readable?
  mode & 0o444 > 0
end
realpath() click to toggle source
# File lib/epitools/path.rb, line 1374
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 312
def relative
  relative_to(pwd)
end
relative?() click to toggle source

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

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
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 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
Also aliased as: ren, rename_to
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 1058
def rename!(arg)
  update(rename(arg))
end
Also aliased as: ren!
rename_to(arg)
Alias for: rename
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 266
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 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
Also aliased as: delete!, unlink!, remove!
sha1() click to toggle source

Checksums

# File lib/epitools/path.rb, line 1274
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 1279
def sha2
  Digest::SHA2.file(self).hexdigest
end
Also aliased as: sha2sum
sha256() click to toggle source
# File lib/epitools/path.rb, line 1289
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 733
def siblings
  Path[dir].ls - [self]
end
size() click to toggle source
# File lib/epitools/path.rb, line 373
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 489
def sort_attrs
  [(filename ? 1 : 0), path.downcase]
end
startswith(s) click to toggle source
# File lib/epitools/path.rb, line 1367
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 1629
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 741
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 1267
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 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
unmarshal() click to toggle source
# File lib/epitools/path.rb, line 691
def unmarshal
  read.unmarshal
end
update(other) click to toggle source
# File lib/epitools/path.rb, line 283
def update(other)
  @dirs = other.dirs
  @base = other.base
  @ext  = other.ext
end
uri?() click to toggle source
# File lib/epitools/path.rb, line 462
def uri?; false; end
url?() click to toggle source
# File lib/epitools/path.rb, line 463
def url?; uri?; end
writable?() click to toggle source
# File lib/epitools/path.rb, line 422
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 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
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 980
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 928
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 970
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 941
def write_yaml(object)
  write object.to_yaml
end
xattrs()
Alias for: attrs
zopen(mode="rb", **opts) { |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 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

arg_to_path(arg) click to toggle source

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