class RbFind

Usage

See the README file or “rbfind -h” for a documentation of the command line tool.

In Ruby programs, you may call:

RbFind.open                do |f| puts f.path end
RbFind.open "dir"          do |f| puts f.path end
RbFind.open "dir1", "dir2" do |f| puts f.path end
RbFind.open %w(dir1 dir2)  do |f| puts f.path end
RbFind.open "dir", :max_depth => 3 do |f| puts f.path end

# more terse
RbFind.run       do puts path end
RbFind.run "dir" do puts path end

File properties

f.name          # file name (*)
f.path          # file path relative to working directory (*)
f.fullpath      # full file path (*)
f.path!         # directories with a slash appended (*)
f.fullpath!     # directories with a slash appended (*)
f.dirname       # dirname of path
f.ext           # file name extension
f.without_ext   # file name without extension
f.depth         # step depth
f.hidden?       # filename starting with "." (No Windows version, yet.)
f.visible?      # not hidden?
f.stat          # file status information (File::Stat object)
f.mode          # access mode (like 0755, 0644)
f.age           # age in seconds since walk started
f.age_s         #   dto. (alias)
f.age_secs      #   dto. (alias)
f.age_m         # age in minutes
f.age_mins      #   dto. (alias)
f.age_h         # age in hours
f.age_hours     #   dto. (alias)
f.age_d         # age in days
f.age_days      #   dto. (alias)
f.user          # owner
f.owner         #   dto. (alias)
f.group         # group owner
f.readlink      # symlink pointer or nil (*)
f.broken_link?  # what you expect
f.arrow         # ls-style "-> symlink" suffix (*)

                # (*) = colored version available (see below)

f.empty?               # directory is empty
f.entries              # directory entries

f.open  { |o| ... }    # open file
f.read n = nil         # read first n bytes, nil reads to eof
f.lines { |l,i| ... }  # open file and yield each |line,lineno|
f.grep re              # lines with `l =~ re and colsep path, i, l'
f.binary? n = 1   # test whether first n blocks contain null characters
f.bin?            # alias for binary?

f.vimswap?      # it is a Vim swapfile

Further will be redirected to the stat object (selective):

f.directory?
f.executable?
f.file?
f.pipe?
f.socket?
f.symlink?

f.readable?
f.writable?
f.size
f.zero?

f.uid
f.gid
f.owned?
f.grpowned?

f.dir?         # alias for f.directory?

Derivated from stat:

f.stype        # one-letter (short) version of ftype
f.modes        # rwxr-xr-x style modes

f.filesize                    # returns size for files, else nil
f.filesize { |s| s > 1024 }   # returns block result for files

Actions

f.prune   # do not descend directory; abort current entry
f.novcs   # omit .svn, CVS and .git directories

f.colsep path, ...   # output parameters in a line separated by colons
f.col_sep            #   dto. (alias)
f.tabsep path, ...   # separate by tabs
f.tab_sep            #
f.spcsep path, ...   # separate by spaces
f.spc_sep            #
f.spacesep path, ... #
f.space_sep          #
f.csv sep, path, ... # separate by user-defined separator

f.rename newname   # rename, but leave it in the same directory

Color support

f.cname       # colorized name
f.cpath       # colorized path
f.cpath!      # colorized path!
f.cfullpath   # colorized fullpath
f.cfullpath!  # colorized fullpath!
f.creadlink   # colored symlink pointer
f.carrow      # colored "-> symlink" suffix

f.color arg   # colorize argument
f.colour arg  # alias

RbFind.colors str      # define colors
RbFind.colours str     # alias

Default color setup is "xxHbexfxcxdxbxegedabagacadAx".
In case you did not call RbFind.colors, the environment variables
RBFIND_COLORS and RBFIND_COLOURS are looked up. If neither is given
but LSCOLORS is set, the fields 2-13 default to that.

The letters mean:
  a = black, b = red, c = green, d = brown, e = blue,
  f = magenta, g = cyan, h = light grey
  upper case = bold (resp. dark grey, yellow)

  first character = foreground, second character = background

The character pairs map the following types:
   0  regular file
   1  nonexistent (broken link)
   2  directory
   3  symbolic link
   4  socket
   5  pipe
   6  executable
   7  block special
   8  character special
   9  executable with setuid bit set
  10  executable with setgid bit set
  11  directory writable to others, with sticky bit
  12  directory writable to others, without sticky bit
  13  whiteout
  14  unknown

f.suffix      # ls-like suffixes |@=/%* for pipe, ..., executable

Examples

Find them all:

RbFind.open do |f| puts f.path end

Omit version control:

RbFind.open "myproject" do |f|
  f.prune if f.name == ".svn"
  puts f.path
end

# or even
RbFind.open "myproject" do |f|
  f.novcs
  puts f.path
end

Mention directory contents before directory itself:

RbFind.open "myproject", :depth => true do |f|
  puts f.path
end

Limit search depth:

RbFind.open :max_depth => 2 do |f|
  puts f.path
end

Unsorted (alphabetical sort is default):

RbFind.open :sort => false do |f|
  puts f.path
end

Reverse sort:

RbFind.open :sort => -1 do |f|
  puts f.path
end

Sort without case sensitivity and preceding dot:

s = proc { |x| x =~ /^\.?/ ; $'.downcase }
RbFind.open :sort => s do |f|
  puts f.path
end

Constants

DEFAULT_COLORS
VERSION

Attributes

count[R]
start[R]
wd[R]

Public Class Methods

new(path, count, params = nil, &block) click to toggle source
# File lib/rbfind.rb, line 283
def initialize path, count, params = nil, &block
  @levels = []
  @block = block

  if params then
    params = params.dup
    dl = :do_level_depth if params.delete :depth
    md = params.delete :max_depth ; @max_depth = md.to_i if md
    st = params.delete :sort ; @sort = sort_parser st
    @follow = params.delete :follow
    @error = params.delete :error
    params.empty?  or
      raise RuntimeError, "Unknown parameter(s): #{params.keys.join ','}."
  end
  @do_level = method dl||:do_level

  @start, @count = Time.now, count
  @wd = Dir.getwd
  if path then
    File.lstat path
    @wd = nil unless absolute_path? path
    @levels.push path
    walk
  else
    build_path
    scan_dir
  end
end

Public Instance Methods

age() click to toggle source
# File lib/rbfind.rb, line 372
def age ; @start - stat.mtime ; end
Also aliased as: age_secs
age_d()
Alias for: age_days
age_days() click to toggle source
# File lib/rbfind.rb, line 386
def age_days  ; age / DAY    ; end
Also aliased as: age_d
age_h()
Alias for: age_hours
age_hours() click to toggle source
# File lib/rbfind.rb, line 384
def age_hours ; age / HOUR   ; end
Also aliased as: age_h
age_m()
Alias for: age_mins
age_mins() click to toggle source
# File lib/rbfind.rb, line 382
def age_mins  ; age / MINUTE ; end
Also aliased as: age_m
age_s()
Alias for: age_secs
age_secs()
Also aliased as: age_s
Alias for: age
arrow() click to toggle source
# File lib/rbfind.rb, line 362
def arrow
  ARROW + (File.readlink @path) if stat.symlink?
end
bin?(n = 1)
Alias for: binary?
binary?( n = 1) → true or false click to toggle source

Test whether the first n blocks contain null characters.

# File lib/rbfind.rb, line 664
def binary? n = 1
  open { |file|
    loop do
      if n then
        break if n <= 0
        n -= 1
      end
      b = file.read BLOCK_SIZE
      b or break
      return true if b[ "\0"]
    end
  }
  false
end
Also aliased as: bin?
carrow() click to toggle source
# File lib/rbfind.rb, line 366
def carrow
  r = creadlink
  ARROW + r if r
end
cfullpath() click to toggle source
# File lib/rbfind.rb, line 463
def cfullpath  ; color fullpath  ; end
cfullpath!() click to toggle source
# File lib/rbfind.rb, line 465
def cfullpath! ; color fullpath! ; end
cname() click to toggle source
# File lib/rbfind.rb, line 461
def cname      ; color name      ; end
col_sep(*args)
Alias for: colsep
color(arg) click to toggle source
# File lib/rbfind.rb, line 467
def color arg
  col_stat arg, stat
end
Also aliased as: colour
colored(arg, num) click to toggle source
# File lib/rbfind.rb, line 494
def colored arg, num
  @cols or colors col_str
  "\e[#{@cols[num]}m#{arg}\e[m"
end
colors(str) click to toggle source
# File lib/rbfind.rb, line 476
def colors str
  @cols = []
  str.scan /(.)(.)/i do
    fg, bg = $~.captures.map { |x| x.downcase.ord - ?a.ord }
    a = []
    case fg
      when 0..7 then a.push 30 + fg
    end
    a.push 1 if $1 == $1.upcase
    case bg
      when 0..7 then a.push 40 + bg
    end
    e = a.join ";"
    @cols.push e
  end
end
colour(arg)
Alias for: color
colsep(*args) click to toggle source
# File lib/rbfind.rb, line 718
def colsep *args
  csv COLON, *args
end
Also aliased as: col_sep
contains?( name) → true or false click to toggle source

Check whether a directory contains an entry.

# File lib/rbfind.rb, line 575
def contains? name
  c = File.join @path, name
  File.exists? c
end
cpath() click to toggle source
# File lib/rbfind.rb, line 462
def cpath      ; color path      ; end
cpath!() click to toggle source
# File lib/rbfind.rb, line 464
def cpath!     ; color path!     ; end
csv(sep, *args) click to toggle source
# File lib/rbfind.rb, line 735
def csv sep, *args
  e = args.join sep
  puts e
end
depth() click to toggle source
# File lib/rbfind.rb, line 332
def depth ; @levels.size ; end
dir?() click to toggle source
# File lib/rbfind.rb, line 399
def dir? ; stat.directory? ; end
dirname() click to toggle source
# File lib/rbfind.rb, line 324
def dirname
  d = File.dirname @fullpath
  File.basename d
end
empty?() → true or false click to toggle source

Look up if the directory is empty. If the object is not a directory, nil is returned.

# File lib/rbfind.rb, line 564
def empty?
  read_dir.each! { |f| return false }
  true
rescue Errno::ENOTDIR
end
entires() → ary click to toggle source

Return all entries in an array. If the object is not a directory, nil is returned.

# File lib/rbfind.rb, line 586
def entries
  read_dir.entries!
rescue Errno::ENOTDIR
end
ext() click to toggle source
# File lib/rbfind.rb, line 329
def ext         ; File.extname name ; end
filesize → nil or int click to toggle source
filesize { |size| ... } → obj

Returns the files size. When the object is not a regular file, nil will be returned or the block will not be called.

# File lib/rbfind.rb, line 452
def filesize
  if block_given? then
    yield stat.size if file?
  else
    stat.size if file?
  end
end
fullpath() click to toggle source
# File lib/rbfind.rb, line 320
def fullpath  ; @fullpath    ; end
fullpath!() click to toggle source
# File lib/rbfind.rb, line 322
def fullpath! ; append_slash @fullpath ; end
grep(re, color = nil) click to toggle source
# File lib/rbfind.rb, line 642
def grep re, color = nil
  case color
    when /\A\d+(?:;\d+)*\z/, nil, false then
    when true then color = "31;1"  # red
    else           raise "Illegal color spec: #{color}"
  end
  lines { |l,i|
    l =~ re or next
    if color then
      l = "#$`\e[#{color}m#$&\e[m#$'"
    end
    colsep @path, i, l
  }
end
group() → str click to toggle source

Return group name or gid as string if unavailable.

# File lib/rbfind.rb, line 552
def group
  g = stat.gid
  (Etc.getgrgid g).name rescue g.to_s
end
hidden?() click to toggle source
# File lib/rbfind.rb, line 334
def hidden?  ; name =~ /^\./ ; end
lines { |l,i| ... } → nil click to toggle source

Yield line by line together with the line number i.

# File lib/rbfind.rb, line 628
def lines
  block_given? or return lines do end
  open { |file|
    n = 0
    file.each_line { |l|
      l.chomp!
      n += 1
      set_predefs l, n
      yield l, n
    }
    n
  }
end
mode() click to toggle source
# File lib/rbfind.rb, line 338
def mode ; stat.mode        ; end
modes() → str click to toggle source
# File lib/rbfind.rb, line 423
def modes
  m = stat.mode
  r = ""
  3.times {
    h = m & 07
    m >>= 3
    r.insert 0, ((h & 01).nonzero? ? "x" : "-")
    r.insert 0, ((h & 02).nonzero? ? "w" : "-")
    r.insert 0, ((h & 04).nonzero? ? "r" : "-")
  }
  if (m & 04).nonzero? then
    r[ 2] = r[ 2, 1] == "x" ? "s" : "S"
  end
  if (m & 02).nonzero? then
    r[ 5] = r[ 5, 1] == "x" ? "s" : "S"
  end
  if (m & 01).nonzero? then
    r[ 8] = r[ 8, 1] == "x" ? "t" : "T"
  end
  r
end
name() click to toggle source
# File lib/rbfind.rb, line 318
def name      ; @levels.last ; end
no_vcs()
Alias for: novcs
novcs() → nil click to toggle source

Perform prune if the current object is a CVS, Subversion or Git directory.

# File lib/rbfind.rb, line 698
def novcs
  prune if %w(CVS .svn .git).include? name
end
Also aliased as: no_vcs
now() click to toggle source
# File lib/rbfind.rb, line 371
def now ; @start ; end
open(*args, &block) click to toggle source
# File lib/rbfind.rb, line 260
def open *args, &block
  params = case args.last
    when Hash then args.pop
  end
  args.flatten!
  if args.any? then
    count = 0
    args.each do |path|
      f = new path, count, params, &block
      count = f.count
    end
  else
    f = new nil, 0, params, &block
    f.count
  end
end
owner()
Alias for: user
path() click to toggle source
# File lib/rbfind.rb, line 319
def path      ; @path        ; end
path!() click to toggle source
# File lib/rbfind.rb, line 321
def path!     ; append_slash @path     ; end
prune() → (does not return) click to toggle source

Abandon the current object (directory) and ignore all subdirectories.

# File lib/rbfind.rb, line 690
def prune ; raise Prune ; end
read( n = nil) → str or nil click to toggle source
read( n = nil) { |b| ... } → nil

Read the first n bytes or return nil for others than regular files. nil reads to end of file. If a block is given, chonks of n bytes (or all) will be yielded.

# File lib/rbfind.rb, line 611
def read n = nil
  open { |o|
    if block_given? then
      while (r = o.read n) do
        yield r
      end
    else
      o.read n
    end
  }
end
rename(newname) click to toggle source
# File lib/rbfind.rb, line 740
def rename newname
  p = @path
  nb = File.basename newname
  newname == nb or raise RuntimeError,
        "#{self.class}: rename to `#{newname}' may not be a path."
  @levels.pop
  @levels.push newname
  build_path
  File.rename p, @path
  nil
end
run(*args, &block) click to toggle source
# File lib/rbfind.rb, line 276
def run *args, &block
  open *args do |f| f.instance_eval &block end
end
space_sep(*args)
Alias for: spc_sep
spacesep(*args)
Alias for: spcsep
spc_sep(*args)
Also aliased as: space_sep
Alias for: spcsep
spcsep(*args) click to toggle source
# File lib/rbfind.rb, line 728
def spcsep *args
  csv " ", *args
end
Also aliased as: spc_sep, spacesep
stat() click to toggle source
# File lib/rbfind.rb, line 337
def stat ; File.lstat @path ; end
stype() → str click to toggle source
# File lib/rbfind.rb, line 404
def stype
  m = stat.mode >> 12 rescue nil
  case m
    when 001 then "p"
    when 002 then "c"
    when 004 then "d"
    when 006 then "b"
    when 010 then "-"
    when 012 then "l"
    when 014 then "s"
    when 016 then "w"
    when nil then "#"
    else          "?"
  end
end
suffix() → str click to toggle source
# File lib/rbfind.rb, line 519
def suffix
  m = stat.mode >> 12 rescue nil
  case m
    when 001 then "|"
    when 002 then " "
    when 004 then "/"
    when 006 then " "
    when 010 then stat.executable? ? "*" : " "
    when 012 then "@"
    when 014 then "="
    when 016 then "%"
    else          "?"
  end
end
tab_sep(*args)
Alias for: tabsep
tabsep(*args) click to toggle source
# File lib/rbfind.rb, line 723
def tabsep *args
  csv "\t", *args
end
Also aliased as: tab_sep
user() → str click to toggle source

Return user name or uid as string if unavailable.

# File lib/rbfind.rb, line 541
def user
  u = stat.uid
  (Etc.getpwuid u).name rescue u.to_s
end
Also aliased as: owner
vimswap? → true or false click to toggle source

Check whether the current object is a Vim swapfile.

# File lib/rbfind.rb, line 708
def vimswap?
  if name =~ /\A(\..+)?\.sw[a-z]\z/i then
    mark = read 5
    mark == "b0VIM"
  end
end
visible?() click to toggle source
# File lib/rbfind.rb, line 335
def visible? ; not hidden?   ; end
without_ext() click to toggle source
# File lib/rbfind.rb, line 330
def without_ext ; name[ /^(.+?)(?:\.[^.]+)?$/, 1 ].to_s ; end

Private Instance Methods

absolute_path?(p) click to toggle source
# File lib/rbfind.rb, line 770
def absolute_path? p
  loop do
    q = File.dirname p
    break if q == p
    p = q
  end
  p != Dir::CUR_DIR
end
append_slash(s ;) click to toggle source
# File lib/rbfind.rb, line 314
def append_slash s ; (File.directory? s) ? (File.join s, "") : s ; end
build_path() click to toggle source
# File lib/rbfind.rb, line 779
def build_path
  @path = File.join @levels
  @fullpath = if @wd then
    File.join @wd, @path
  else
    @path
  end
  if @path.empty? then @path = Dir::CUR_DIR end
end
call_block() click to toggle source
# File lib/rbfind.rb, line 819
def call_block
  set_predefs name, count
  @block.call self
end
col_stat(arg, s) click to toggle source
# File lib/rbfind.rb, line 878
def col_stat arg, s
  m = s.mode if s
  code = case m && m >> 12
    when 001 then                        5
    when 002 then                        8
    when 004 then
      if (m & 0002).nonzero? then
        if (m & 01000).nonzero? then    11
        else                            12
        end
      else                               2
      end
    when 006 then                        7
    when 010 then
      col_type or          if (m & 0111).nonzero? then
        if (m & 04000).nonzero? then     9
        elsif (m & 02000).nonzero? then 10
        else                             6
        end
      else                               0
      end
    when 012 then                        3
    when 014 then                        4
    when 016 then                       13
    when nil then                        1
    else                                14
  end
  self.class.colored arg, code
end
col_str() click to toggle source
# File lib/rbfind.rb, line 502
def col_str
  ENV[ "RBFIND_COLORS"] || ENV[ "RBFIND_COLOURS"] || (
    env = DEFAULT_COLORS.dup
    els = ENV[ "LSCOLORS"]
    if els then
      env[ 2*2, els.length] = els
    end
    env
  )
end
col_type() click to toggle source
# File lib/rbfind.rb, line 909
def col_type
  # Overwrite this to define custom colors
  # Example:
  #   case ext
  #     when ".png", /\.jpe?g$/, /\.tiff?$/ then 15
  #     when /\.tar\.(gz|bz2)$/             then 16
  #   end
end
do_level() click to toggle source
# File lib/rbfind.rb, line 796
def do_level
  begin
    call_block
  rescue Prune
    return
  end
  scan_dir
end
do_level_depth() click to toggle source
# File lib/rbfind.rb, line 805
def do_level_depth
  begin
    path, fullpath = @path, @fullpath
    scan_dir
  ensure
    @path, @fullpath = path, fullpath
  end
  begin
    call_block
  rescue Prune
    raise RuntimeError, "#{self.class}: prune doesn't work with :depth."
  end
end
find(*args) click to toggle source
# File lib/rbfind.rb, line 919
def find *args
  raise NotImplementedError, "This is not the standard Find."
end
handle_error(err = nil) { || ... } click to toggle source
# File lib/rbfind.rb, line 865
def handle_error err = nil
  yield
rescue err||StandardError
  if @error.respond_to? :call then
    @error.call
  elsif @error then
    instance_eval @error
  else
    raise
  end
  nil
end
method_missing(sym, *args, &block) click to toggle source
Calls superclass method
# File lib/rbfind.rb, line 391
def method_missing sym, *args, &block
  stat.send sym, *args, &block
rescue NoMethodError
  super
end
read_dir() click to toggle source
# File lib/rbfind.rb, line 830
def read_dir
  handle_error Errno::EACCES do
    Dir.new @path
  end
end
scan_dir() click to toggle source
# File lib/rbfind.rb, line 836
def scan_dir
  return unless File.directory? @path
  if File.symlink? @path then
    return unless @follow and handle_error do
      d = @path
      while d != Dir::CUR_DIR do
        d, = File.split d
        raise "circular recursion in #@path" if File.identical? d, @path
      end
      true
    end
  end
  dir = (read_dir or return).entries!
  if @sort.respond_to? :call then
    dir = dir.sort_by &@sort
  elsif @sort and @sort.nonzero? then
    dir.sort!
    dir.reverse! if @sort < 0
  end
  dir.each { |f|
    begin
      @levels.push f
      walk
    ensure
      @levels.pop
    end
  }
end
set_predefs(l, n) click to toggle source
# File lib/rbfind.rb, line 824
def set_predefs l, n
  b = @block.binding
  b.local_variable_set "_", [ l, n]
  b.eval "$_, $. = *_"
end
sort_parser(st) click to toggle source
# File lib/rbfind.rb, line 754
def sort_parser st
  case st
    when Proc    then st
    when Numeric then st
    when true    then +1
    when false   then  0
    when nil     then +1
    else
      case st.to_s
        when "^", "reverse", /^desc/, /^-/ then -1
        when "unsorted", "*"               then  0
        else                                    +1
      end
  end
end
walk() click to toggle source
# File lib/rbfind.rb, line 789
def walk
  return if @max_depth and depth > @max_depth
  build_path
  @count += 1
  @do_level.call
end