Class: Path

Inherits:
Object show all
Includes:
Comparable
Defined in:
lib/epitools/path.rb

Overview

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!

Direct Known Subclasses

Relative, URL

Defined Under Namespace

Classes: Relative, URL

Constant Summary collapse

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.

%w[
  mkdir
  mkdir_p
  sha1
  sha2
  md5
  rm
  truncate
  realpath
  mv/1
  move/1
  chmod/1
  chown/1
  chown_R/1
  chmod_R/1
].each do |spec|
  method, cardinality = spec.split("/")
  cardinality = cardinality.to_i

  class_eval %{
    def self.#{method}(path#{", *args" if cardinality > 0})
      Path[path].#{method}#{"(*args)" if cardinality > 0}
    end
  }
end
PATH_SEPARATOR =
":"
BINARY_EXTENSION =
""

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(newpath, hints = {}) ⇒ Path

Initializers



125
126
127
# File 'lib/epitools/path.rb', line 125

def initialize(newpath, hints={})
  send("path=", newpath, hints)
end

Instance Attribute Details

#baseObject Also known as: basename

The filename without an extension



115
116
117
# File 'lib/epitools/path.rb', line 115

def base
  @base
end

#dirsObject

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



112
113
114
# File 'lib/epitools/path.rb', line 112

def dirs
  @dirs
end

#extObject Also known as: extname, extension

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



118
119
120
# File 'lib/epitools/path.rb', line 118

def ext
  @ext
end

Class Method Details

.[](path) ⇒ Object



143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
# File 'lib/epitools/path.rb', line 143

def self.[](path)
  case path
  when Path
    path
  when String

    if path =~ %r{^[a-z\-]+://}i # URL?
      Path::URL.new(path)

    elsif path =~ /^javascript:/
      Path::JS.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) ⇒ Object

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.



1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
# File 'lib/epitools/path.rb', line 1296

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) ⇒ Object



135
136
137
# File 'lib/epitools/path.rb', line 135

def self.escape(str)
  Shellwords.escape(str)
end

.expand_path(orig_path) ⇒ Object

Same as File.expand_path, except preserves the trailing ‘/’.



1247
1248
1249
1250
1251
# File 'lib/epitools/path.rb', line 1247

def self.expand_path(orig_path)
  new_path = File.expand_path orig_path
  new_path << "/" if orig_path.endswith "/"
  new_path
end

.getfattr(path) ⇒ Object

Read xattrs from file (requires “getfattr” to be in the path)



513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
# File 'lib/epitools/path.rb', line 513

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 = {}) ⇒ Object



139
140
141
# File 'lib/epitools/path.rb', line 139

def self.glob(str, hints={})
  Dir[str].map { |entry| new(entry, hints) }
end

.homeObject



1273
1274
1275
# File 'lib/epitools/path.rb', line 1273

def self.home
  Path[ENV['HOME']]
end

.ln_s(src, dest) ⇒ Object



1319
1320
1321
1322
# File 'lib/epitools/path.rb', line 1319

def self.ln_s(src, dest)
  FileUtils.ln_s(src, dest)
  Path[dest]
end

.ls(path) ⇒ Object



1315
# File 'lib/epitools/path.rb', line 1315

def self.ls(path); Path[path].ls  end

.ls_r(path) ⇒ Object



1317
# File 'lib/epitools/path.rb', line 1317

def self.ls_r(path); Path[path].ls_r; end

.popdObject



1286
1287
1288
1289
# File 'lib/epitools/path.rb', line 1286

def self.popd
  @@dir_stack ||= [pwd]
  @@dir_stack.pop
end

.pushdObject



1281
1282
1283
1284
# File 'lib/epitools/path.rb', line 1281

def self.pushd
  @@dir_stack ||= []
  @@dir_stack.push pwd
end

.pwdObject



1277
1278
1279
# File 'lib/epitools/path.rb', line 1277

def self.pwd
  Path.new expand_path(Dir.pwd)
end

.setfattr(path, key, value) ⇒ Object

Set xattrs on a file (requires “setfattr” to be in the path)



541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
# File 'lib/epitools/path.rb', line 541

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") ⇒ Object



1264
1265
1266
1267
1268
1269
1270
1271
# File 'lib/epitools/path.rb', line 1264

def self.tmpdir(prefix="tmp")
  t = tmpfile

  # FIXME: These operations should be atomic
  t.rm; t.mkdir

  t
end

.tmpfile(prefix = "tmp") {|path| ... } ⇒ Object

TODO: Remove the tempfile when the Path object is garbage collected or freed.

Yields:



1256
1257
1258
1259
1260
# File 'lib/epitools/path.rb', line 1256

def self.tmpfile(prefix="tmp")
  path = Path[ Tempfile.new(prefix).path ]
  yield path if block_given?
  path
end

.which(bin, *extras) ⇒ Object

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.)


1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
# File 'lib/epitools/path.rb', line 1343

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

Instance Method Details

#/(other) ⇒ Object

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



499
500
501
502
503
504
# File 'lib/epitools/path.rb', line 499

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

#<=>(other) ⇒ Object



467
468
469
470
471
472
473
474
475
476
# File 'lib/epitools/path.rb', line 467

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) ⇒ Object



478
479
480
# File 'lib/epitools/path.rb', line 478

def ==(other)
  self.path == other.to_s
end

#=~(pattern) ⇒ Object

Match the full path against a regular expression



1090
1091
1092
# File 'lib/epitools/path.rb', line 1090

def =~(pattern)
  to_s =~ pattern
end

#[](key) ⇒ Object

Retrieve one of this file’s xattrs



589
590
591
# File 'lib/epitools/path.rb', line 589

def [](key)
  attrs[key]
end

#[]=(key, value) ⇒ Object

Set this file’s xattr



596
597
598
599
# File 'lib/epitools/path.rb', line 596

def []=(key, value)
  Path.setfattr(path, key, value)
  @attrs = nil # clear cached xattrs
end

#append(data = nil) ⇒ Object Also known as: <<

Append data to this file (accepts a string, an IO, or it can yield the file handle to a block.)



693
694
695
696
697
698
699
700
701
702
703
704
705
706
# File 'lib/epitools/path.rb', line 693

def append(data=nil)
  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

#atimeObject



383
384
385
# File 'lib/epitools/path.rb', line 383

def atime
  lstat.atime
end

#atime=(new_atime) ⇒ Object



387
388
389
390
391
# File 'lib/epitools/path.rb', line 387

def atime=(new_atime)
  File.utime(new_atime, mtime, path)
  @lstat = nil
  new_atime
end

#attrsObject Also known as: xattrs

Return a hash of all of this file’s xattrs. (Metadata key=>valuse pairs, supported by most modern filesystems.)



565
566
567
# File 'lib/epitools/path.rb', line 565

def attrs
  @attrs ||= Path.getfattr(path)
end

#attrs=(new_attrs) ⇒ Object

Set this file’s xattrs. (Optimized so that only changed attrs are written to disk.)



573
574
575
576
577
578
579
580
581
582
583
584
# File 'lib/epitools/path.rb', line 573

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
      raise "Error: Can't use a #{new.class} as an xattr value. Try passing a String."
    end
  end
end

#backup!Object

Rename this file, “filename.ext”, to “filename.ext.bak”. (Does not modify this Path object.)



920
921
922
# File 'lib/epitools/path.rb', line 920

def backup!
  rename(backup_file)
end

#backup_fileObject

Return a copy of this Path with “.bak” at the end



904
905
906
# File 'lib/epitools/path.rb', line 904

def backup_file
  with(:filename => filename+".bak")
end

#broken_symlink?Boolean

Returns:

  • (Boolean)


423
424
425
# File 'lib/epitools/path.rb', line 423

def broken_symlink?
  File.symlink?(path) and not File.exists?(path)
end

#cd(&block) ⇒ Object

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.



818
819
820
# File 'lib/epitools/path.rb', line 818

def cd(&block)
  Path.cd(path, &block)
end

#child_of?(parent) ⇒ Boolean

Returns:

  • (Boolean)


446
447
448
# File 'lib/epitools/path.rb', line 446

def child_of?(parent)
  parent.parent_of? self
end

#chmod(mode) ⇒ Object

Owners and permissions



968
969
970
971
# File 'lib/epitools/path.rb', line 968

def chmod(mode)
  FileUtils.chmod(mode, self)
  self
end

#chmod_R(mode) ⇒ Object



979
980
981
982
983
984
985
986
# File 'lib/epitools/path.rb', line 979

def chmod_R(mode)
  if directory?
    FileUtils.chmod_R(mode, self)
    self
  else
    raise "Not a directory."
  end
end

#chown(usergroup) ⇒ Object



973
974
975
976
977
# File 'lib/epitools/path.rb', line 973

def chown(usergroup)
  user, group = usergroup.split(":")
  FileUtils.chown(user, group, self)
  self
end

#chown_R(usergroup) ⇒ Object



988
989
990
991
992
993
994
995
996
# File 'lib/epitools/path.rb', line 988

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) ⇒ Object



953
954
955
956
# File 'lib/epitools/path.rb', line 953

def cp(dest)
  FileUtils.cp(path, dest)
  dest
end

#cp_r(dest) ⇒ Object



948
949
950
951
# File 'lib/epitools/path.rb', line 948

def cp_r(dest)
  FileUtils.cp_r(path, dest) #if Path[dest].exists?
  dest
end

#ctimeObject



379
380
381
# File 'lib/epitools/path.rb', line 379

def ctime
  lstat.ctime
end

#deflate(level = nil) ⇒ Object Also known as: gzip

gzip the file, returning the result as a string



1037
1038
1039
# File 'lib/epitools/path.rb', line 1037

def deflate(level=nil)
  Zlib.deflate(read, level)
end

#dirObject Also known as: dirname, directory

The current directory (with a trailing /)



306
307
308
309
310
311
312
313
314
315
316
# File 'lib/epitools/path.rb', line 306

def dir
  if dirs
    if relative?
      File.join(*dirs)
    else
      File.join("", *dirs)
    end
  else
    nil
  end
end

#dir=(newdir) ⇒ Object Also known as: dirname=, directory=



222
223
224
225
226
227
# File 'lib/epitools/path.rb', line 222

def dir=(newdir)
  dirs  = File.expand_path(newdir).split(File::SEPARATOR)
  dirs  = dirs[1..-1] if dirs.size > 0

  @dirs = dirs
end

#dir?Boolean Also known as: directory?

Returns:

  • (Boolean)


411
412
413
# File 'lib/epitools/path.rb', line 411

def dir?
  File.directory? path
end

#each_chunk(chunk_size = 2**14) ⇒ Object

Read the contents of the file, yielding it in 16k chunks. (default chunk size is 16k)



622
623
624
625
626
# File 'lib/epitools/path.rb', line 622

def each_chunk(chunk_size=2**14)
  open do |f|
    yield f.read(chunk_size) until f.eof?
  end
end

#each_lineObject Also known as: lines

All the lines in this file, chomped.



632
633
634
# File 'lib/epitools/path.rb', line 632

def each_line
  io.each_line
end

#endswith(s) ⇒ Object



1106
# File 'lib/epitools/path.rb', line 1106

def endswith(s); path.endswith(s); end

#executable?Boolean Also known as: exe?

Returns:

  • (Boolean)


398
399
400
# File 'lib/epitools/path.rb', line 398

def executable?
  mode & 0o111 > 0
end

#exists?Boolean Also known as: exist?

fstat

Returns:

  • (Boolean)


352
353
354
# File 'lib/epitools/path.rb', line 352

def exists?
  File.exists? path
end

#extsObject



334
335
336
337
338
# File 'lib/epitools/path.rb', line 334

def exts
  extensions = basename.split('.')[1..-1]
  extensions += [@ext] if @ext
  extensions
end

#file?Boolean

Returns:

  • (Boolean)


415
416
417
# File 'lib/epitools/path.rb', line 415

def file?
  File.file? path
end

#filenameObject



318
319
320
321
322
323
324
325
326
327
328
# File 'lib/epitools/path.rb', line 318

def filename
  if base
    if ext
      base + "." + ext
    else
      base
    end
  else
    nil
  end
end

#filename=(newfilename) ⇒ Object



204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
# File 'lib/epitools/path.rb', line 204

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

#grep(pat) ⇒ Object



637
638
639
640
641
642
643
# File 'lib/epitools/path.rb', line 637

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!Object

Quickly gunzip a file, creating a new file, without removing the original, and returning a Path to that new file.



1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
# File 'lib/epitools/path.rb', line 1073

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) ⇒ Object

Quickly gzip a file, creating a new .gz file, without removing the original, and returning a Path to that new file.



1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
# File 'lib/epitools/path.rb', line 1055

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

#inflateObject Also known as: gunzip

gunzip the file, returning the result as a string



1046
1047
1048
# File 'lib/epitools/path.rb', line 1046

def inflate
  Zlib.inflate(read)
end

#initialize_copy(other) ⇒ Object



129
130
131
132
133
# File 'lib/epitools/path.rb', line 129

def initialize_copy(other)
  @dirs = other.dirs && other.dirs.dup
  @base = other.base && other.base.dup
  @ext  = other.ext  && other.ext.dup
end

#inspectObject

inspect



344
345
346
# File 'lib/epitools/path.rb', line 344

def inspect
  "#<Path:#{path}>"
end

#join(other) ⇒ Object

Path.join(“anything{}”).path == “/etc/anything{}” (globs ignored)



491
492
493
# File 'lib/epitools/path.rb', line 491

def join(other)
  Path.new File.join(self, other)
end

#ln_s(dest) ⇒ Object



958
959
960
961
962
963
964
# File 'lib/epitools/path.rb', line 958

def ln_s(dest)
  if dest.startswith("/")
    Path.ln_s(self, dest)
  else
    Path.ln_s(self, self / dest )
  end
end

#lsObject



653
654
655
656
657
# File 'lib/epitools/path.rb', line 653

def ls
  Dir.foreach(path).
    reject {|fn| fn == "." or fn == ".." }.
    map {|fn| Path.new(fn) }
end

#ls_dirsObject



666
667
668
669
# File 'lib/epitools/path.rb', line 666

def ls_dirs
  ls.select(&:dir?)
  #Dir.glob("#{path}*/", File::FNM_DOTMATCH).map { |s| Path.new(s, :type=>:dir) }
end

#ls_filesObject



671
672
673
674
# File 'lib/epitools/path.rb', line 671

def ls_files
  ls.select(&:file?)
  #Dir.glob("#{path}*", File::FNM_DOTMATCH).map { |s| Path.new(s, :type=>:file) }
end

#ls_RObject



664
665
666
667
668
# File 'lib/epitools/path.rb', line 664

def ls_r(symlinks=false)
  # glob = symlinks ? "**{,/*/**}/*" : "**/*"
  # Path[File.join(path, glob)]
  Find.find(path).drop(1).map {|fn| Path.new(fn) }
end

#ls_r(symlinks = false) ⇒ Object



659
660
661
662
663
# File 'lib/epitools/path.rb', line 659

def ls_r(symlinks=false)
  # glob = symlinks ? "**{,/*/**}/*" : "**/*"
  # Path[File.join(path, glob)]
  Find.find(path).drop(1).map {|fn| Path.new(fn) }
end

#lstatObject



360
361
362
363
# File 'lib/epitools/path.rb', line 360

def lstat
  @lstat ||= File.lstat self    # to cache, or not to cache? that is the question.
  # File.lstat self                 # ...answer: not to cache!
end

#magicObject

Find the file’s mimetype (by magic)



1141
1142
1143
# File 'lib/epitools/path.rb', line 1141

def magic
  open { |io| MimeMagic.by_magic(io) }
end

#md5Object Also known as: md5sum



1026
1027
1028
# File 'lib/epitools/path.rb', line 1026

def md5
  Digest::MD5.file(self).hexdigest
end

#mimetypeObject Also known as: identify

Find the file’s mimetype (first from file extension, then by magic)



1126
1127
1128
# File 'lib/epitools/path.rb', line 1126

def mimetype
  mimetype_from_ext || magic
end

#mimetype_from_extObject

Find the file’s mimetype (only using the file extension)



1134
1135
1136
# File 'lib/epitools/path.rb', line 1134

def mimetype_from_ext
  MimeMagic.by_extension(ext)
end

#modeObject



365
366
367
# File 'lib/epitools/path.rb', line 365

def mode
  lstat.mode
end

#mtimeObject



369
370
371
# File 'lib/epitools/path.rb', line 369

def mtime
  lstat.mtime
end

#mtime=(new_mtime) ⇒ Object



373
374
375
376
377
# File 'lib/epitools/path.rb', line 373

def mtime=(new_mtime)
  File.utime(atime, new_mtime, path)
  @lstat = nil
  new_mtime
end

#mv(arg) ⇒ Object Also known as: move

Works the same as “rename”, but the destination can be on another disk.



861
862
863
864
865
866
867
868
# File 'lib/epitools/path.rb', line 861

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

#mv!(arg) ⇒ Object Also known as: move!

Moves the file (overwriting the destination if it already exists). Also points the current Path object at the new destination.



882
883
884
# File 'lib/epitools/path.rb', line 882

def mv!(arg)
  update(mv(arg))
end

#nameObject



330
331
332
# File 'lib/epitools/path.rb', line 330

def name
  filename || "#{dirs.last}/"
end

#nicelinesObject



645
646
647
# File 'lib/epitools/path.rb', line 645

def nicelines
  lines.map(&:chomp)
end

#numbered_backup!Object

Rename this file, “filename.ext”, to “filename (1).ext” (or (2), or (3), or whatever number is available.) (Does not modify this Path object.)



912
913
914
# File 'lib/epitools/path.rb', line 912

def numbered_backup!
  rename(numbered_backup_file)
end

#numbered_backup_fileObject

Find a backup filename that doesn’t exist yet by appending “(1)”, “(2)”, etc. to the current filename.



890
891
892
893
894
895
896
897
898
899
# File 'lib/epitools/path.rb', line 890

def numbered_backup_file
  return self unless exists?

  n = 1
  loop do
    new_file = with(:basename => "#{basename} (#{n})")
    return new_file unless new_file.exists?
    n += 1
  end
end

#open(mode = "rb", &block) ⇒ Object Also known as: io, stream

Opening/Reading files



605
606
607
608
609
610
611
# File 'lib/epitools/path.rb', line 605

def open(mode="rb", &block)
  if block_given?
    File.open(path, mode, &block)
  else
    File.open(path, mode)
  end
end

#owner?Boolean

FIXME: Does the current user own this file?

Returns:

  • (Boolean)


394
395
396
# File 'lib/epitools/path.rb', line 394

def owner?
  raise "STUB"
end

#parentObject

Find the parent directory. If the ‘Path` is a filename, it returns the containing directory.



1097
1098
1099
1100
1101
1102
1103
# File 'lib/epitools/path.rb', line 1097

def parent
  if file?
    with(:filename=>nil)
  else
    with(:dirs=>dirs[0...-1])
  end
end

#parent_of?(child) ⇒ Boolean

Returns:

  • (Boolean)


450
451
452
# File 'lib/epitools/path.rb', line 450

def parent_of?(child)
  dirs == child.dirs[0...dirs.size]
end

#parseObject

Parse the file based on the file extension. (Handles json, html, yaml, xml, bson, and marshal.)



734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
# File 'lib/epitools/path.rb', line 734

def parse
  case ext.downcase
  when 'json'
    read_json
  when 'html', 'htm'
    read_html
  when 'yaml', 'yml'
    read_yaml
  when 'xml', 'rdf', 'rss'
    read_xml
  when 'csv'
    read_csv
  when 'marshal'
    read_marshal
  when 'bson'
    read_bson
  else
    raise "Unrecognized format: #{ext}"
  end
end

#pathObject Also known as: to_path, to_str, to_s, pathname

Joins and returns the full path



275
276
277
278
279
280
281
# File 'lib/epitools/path.rb', line 275

def path
  if d = dir
    File.join(d, (filename || "") )
  else
    ""
  end
end

#path=(newpath, hints = {}) ⇒ Object

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.



181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
# File 'lib/epitools/path.rb', line 181

def path=(newpath, hints={})
  if hints[:type] or File.exists? 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

#read(length = nil, offset = nil) ⇒ Object



615
616
617
# File 'lib/epitools/path.rb', line 615

def read(length=nil, offset=nil)
  File.read(path, length, offset)
end

#read_bsonObject



805
806
807
# File 'lib/epitools/path.rb', line 805

def read_bson
  BSON.deserialize(read)
end

#read_csv(opts = {}) ⇒ Object Also known as: from_csv

Parse the file as CSV



785
786
787
# File 'lib/epitools/path.rb', line 785

def read_csv(opts={})
  CSV.open(io, opts).each
end

#read_htmlObject Also known as: from_html



767
768
769
# File 'lib/epitools/path.rb', line 767

def read_html
  Nokogiri::HTML(io)
end

#read_jsonObject Also known as: from_json

Parse the file as JSON



756
757
758
# File 'lib/epitools/path.rb', line 756

def read_json
  JSON.load(io)
end

#read_marshalObject



796
797
798
# File 'lib/epitools/path.rb', line 796

def read_marshal
  Marshal.load(io)
end

#read_xmlObject



791
792
793
# File 'lib/epitools/path.rb', line 791

def read_xml
  Nokogiri::XML(io)
end

#read_yamlObject Also known as: from_yaml

Parse the file as YAML



779
780
781
# File 'lib/epitools/path.rb', line 779

def read_yaml
  YAML.load(io)
end

#readable?Boolean

Returns:

  • (Boolean)


407
408
409
# File 'lib/epitools/path.rb', line 407

def readable?
  mode & 0o444 > 0
end

#realpathObject



1112
1113
1114
# File 'lib/epitools/path.rb', line 1112

def realpath
  Path.new File.realpath(path)
end

#relativeObject

Path relative to current directory (Path.pwd)



295
296
297
# File 'lib/epitools/path.rb', line 295

def relative
  relative_to(pwd)
end

#relative?Boolean

Is this a relative path?

Returns:

  • (Boolean)


286
287
288
289
290
# File 'lib/epitools/path.rb', line 286

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) ⇒ Object



299
300
301
302
303
# File 'lib/epitools/path.rb', line 299

def relative_to(anchor)
  anchor = anchor.to_s
  anchor += "/" unless anchor[/\/$/]
  to_s.gsub(/^#{Regexp.escape(anchor)}/, '')
end

#reload!Object

Reload this path (updates cached values.)



257
258
259
260
261
262
263
264
# File 'lib/epitools/path.rb', line 257

def reload!
  temp = path
  reset!
  self.path = temp
  @attrs = nil

  self
end

#rename(arg) ⇒ Object Also known as: ren

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


847
848
849
850
851
852
853
854
855
# File 'lib/epitools/path.rb', line 847

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!(arg) ⇒ Object Also known as: ren!

Rename the file and change this Path object so that it points to the destination file.



874
875
876
# File 'lib/epitools/path.rb', line 874

def rename!(arg)
  update(rename(arg))
end

#reset!Object

Clear out the internal state of this object, so that it can be reinitialized.



249
250
251
252
# File 'lib/epitools/path.rb', line 249

def reset!
  [:@dirs, :@base, :@ext].each { |var| remove_instance_variable(var) rescue nil  }
  self
end

#rmObject Also known as: delete!, unlink!, remove!

Dangerous methods.



1000
1001
1002
1003
1004
1005
1006
# File 'lib/epitools/path.rb', line 1000

def rm
  if directory? and not symlink?
    Dir.rmdir(self) == 0
  else
    File.unlink(self) == 1
  end
end

#sha1Object

Checksums



1018
1019
1020
# File 'lib/epitools/path.rb', line 1018

def sha1
  Digest::SHA1.file(self).hexdigest
end

#sha2Object



1022
1023
1024
# File 'lib/epitools/path.rb', line 1022

def sha2
  Digest::SHA2.file(self).hexdigest
end

#siblingsObject



676
677
678
# File 'lib/epitools/path.rb', line 676

def siblings
  Path[dir].ls - [self]
end

#sizeObject



356
357
358
# File 'lib/epitools/path.rb', line 356

def size
  File.size path
end

#sort_attrsObject

An array of attributes which will be used sort paths (case insensitive, directories come first)



463
464
465
# File 'lib/epitools/path.rb', line 463

def sort_attrs
  [filename ? 1 : 0, path.downcase]
end

#startswith(s) ⇒ Object



1105
# File 'lib/epitools/path.rb', line 1105

def startswith(s); path.startswith(s); end

#symlink?Boolean

Returns:

  • (Boolean)


419
420
421
# File 'lib/epitools/path.rb', line 419

def symlink?
  File.symlink? path
end


427
428
429
430
431
432
433
434
# File 'lib/epitools/path.rb', line 427

def symlink_target
  target = File.readlink(path.gsub(/\/$/, ''))
  if target.startswith("/")
    Path[target]
  else
    Path[dir] / target
  end
end

#to_PathObject

No-op (returns self)



1358
1359
1360
# File 'lib/epitools/path.rb', line 1358

def to_Path
  self
end

#touchObject



680
681
682
683
# File 'lib/epitools/path.rb', line 680

def touch
  open("a") { }
  self
end

#truncate(offset = 0) ⇒ Object



1011
1012
1013
# File 'lib/epitools/path.rb', line 1011

def truncate(offset=0)
  File.truncate(self, offset) if exists?
end

#typeObject

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?



1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
# File 'lib/epitools/path.rb', line 1155

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

#unmarshalObject



649
650
651
# File 'lib/epitools/path.rb', line 649

def unmarshal
  read.unmarshal
end

#update(other) ⇒ Object



266
267
268
269
270
# File 'lib/epitools/path.rb', line 266

def update(other)
  @dirs = other.dirs
  @base = other.base
  @ext  = other.ext
end

#uri?Boolean

Returns:

  • (Boolean)


438
439
440
# File 'lib/epitools/path.rb', line 438

def uri?
  false
end

#url?Boolean

Returns:

  • (Boolean)


442
443
444
# File 'lib/epitools/path.rb', line 442

def url?
  uri?
end

#writable?Boolean

Returns:

  • (Boolean)


403
404
405
# File 'lib/epitools/path.rb', line 403

def writable?
  mode & 0o222 > 0
end

#write(data = nil) ⇒ Object

Overwrite the data in this file (accepts a string, an IO, or it can yield the file handle to a block.)



712
713
714
715
716
717
718
719
720
721
722
723
724
# File 'lib/epitools/path.rb', line 712

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) ⇒ Object



809
810
811
# File 'lib/epitools/path.rb', line 809

def write_bson(object)
  write BSON.serialize(object)
end

#write_json(object) ⇒ Object

Convert the object to JSON and write it to the file (overwriting the existing file).



762
763
764
# File 'lib/epitools/path.rb', line 762

def write_json(object)
  write object.to_json
end

#write_marshal(object) ⇒ Object



800
801
802
# File 'lib/epitools/path.rb', line 800

def write_marshal(object)
  write object.marshal
end

#write_yaml(object) ⇒ Object

Convert the object to YAML and write it to the file (overwriting the existing file).



774
775
776
# File 'lib/epitools/path.rb', line 774

def write_yaml(object)
  write object.to_yaml
end