class Trop::FileFind

Constants

VERSION

The version of the file-find library

Attributes

atime[RW]

Limits searches by file access time, where the value you supply is the number of days back from the time that the File::Find#find method was called.

ctime[RW]

Limits searches by file change time, where the value you supply is the number of days back from the time that the File::Find#find method was called.

filetest[RW]

An array of two element arrays for storing FileTest methods and their boolean value.

follow[RW]

Controls the behavior of how symlinks are followed. If set to true (the default), then follows the file pointed to. If false, it considers the symlink itself.

ftype[RW]

Limits searches to specific types of files. The possible values here are those returned by the File.ftype method.

group[RW]

Limits searches to files that belong to a specific group, where the group can be either a group name or ID.

inum[RW]

Limits search to a file with a specific inode number.

maxdepth[RW]

Limits search to a maximum depth into the tree relative to the starting search directory.

mindepth[RW]

Limits searches to a minimum depth into the tree relative to the starting search directory.

mount[R]

Limits searches to the same filesystem as the specified directory. For Windows users, this refers to the volume.

mtime[RW]

Limits searches by file modification time, where the value you supply is the number of days back from the time that the File::Find#find method was called.

name[RW]

The name pattern used to limit file searches. The patterns that are legal for Dir.glob are legal here. The default is '*', i.e. everything.

options[RW]

The list of options passed to the constructor and/or used by the Trop::Find#find method.

path[RW]

The starting path(s) for the search. The default is the current directory. This can be a single path or an array of paths.

pattern[RW]

The name pattern used to limit file searches. The patterns that are legal for Dir.glob are legal here. The default is '*', i.e. everything.

pattern=[RW]

The name pattern used to limit file searches. The patterns that are legal for Dir.glob are legal here. The default is '*', i.e. everything.

perm[RW]

Limits searches to files which have permissions that match the octal value that you provide. For purposes of this comparison, only the user, group, and world settings are used.

You may optionally use symbolic permissions, e.g. “g+rw”, “u=rwx”, etc.

MS Windows only recognizes two modes, 0644 and 0444.

previous[R]

The file that matched previously in the current search.

prune[RW]

Skips files or directories that match the string provided as an argument.

size[RW]

If the value passed is an integer, this option limits searches to files that match the size, in bytes, exactly. If a string is passed, you can use the standard comparable operators to match files, e.g. “>= 200” would limit searches to files greater than or equal to 200 bytes.

user[RW]

Limits searches to files that belong to a specific user, where the user can be either a user name or an ID.

Public Class Methods

new(options = {}) click to toggle source

Creates and returns a new File::Find object. The options set for this object serve as the rules for determining what files the File::Find#find method will search for.

In addition to the standard list of valid options, you may also use FileTest methods as options, setting their value to true or false.

Example:

rule = File::Find.new(
   :name      => "*.rb",
   :follow    => false,
   :path      => ['/usr/local/lib', '/opt/local/lib'],
   :readable? => true
)
# File lib/trop/filefind.rb, line 169
def initialize(options = {})
  @options = options

  @atime = nil
  @ctime = nil
  @ftype = nil
  @group = nil
  @follow = true
  @inum = nil
  @links = nil
  @mount = nil
  @mtime = nil
  @perm = nil
  @prune = nil
  @size = nil
  @user = nil

  @previous = nil
  @maxdepth = nil
  @mindepth = nil
  @filetest = []

  validate_and_set_options(options) unless options.empty?

  @filesystem = File.stat(@mount).dev if @mount

  @path ||= Dir.pwd
  @name ||= '*'
end

Public Instance Methods

find() { |file| ... } click to toggle source

Executes the find based on the rules you set for the File::Find object. In block form, yields each file in turn that matches the specified rules. In non-block form it will return an array of matches instead.

Example:

rule = Trop::Find.new(
   :name    => "*.rb",
   :follow  => false,
   :path    => ['/usr/local/lib', '/opt/local/lib']
)

rule.find{ |f|
   puts f
}
# File lib/trop/filefind.rb, line 215
def find
  results = [] unless block_given?
  paths = @path.is_a?(String) ? [@path] : @path # Ruby 1.9.x compatibility

  if @prune
    prune_regex = Regexp.new(@prune)
  else
    prune_regex = nil
  end

  paths.each {|path|
    begin
      Dir.foreach(path) {|file|
        next if file == '.'
        next if file == '..'

        if prune_regex
          next if prune_regex.match(file)
        end

        file = File.join(path, file)

        stat_method = @follow ? :stat : :lstat
        # Skip files we cannot access, stale links, etc.
        begin
          stat_info = File.send(stat_method, file)
        rescue Errno::ENOENT, Errno::EACCES
          next
        rescue Errno::ELOOP
          stat_method = :lstat # Handle recursive symlinks
          retry if stat_method.to_s != 'lstat'
        end

        # We need to escape any brackets in the directory name.
        glob = File.join(File.dirname(file).gsub(/([\[\]])/, '\\\\\1'), @name)

        # Dir[] doesn't like backslashes
        if File::ALT_SEPARATOR
          file.tr!(File::ALT_SEPARATOR, File::SEPARATOR)
          glob.tr!(File::ALT_SEPARATOR, File::SEPARATOR)
        end

        if @mount
          next unless stat_info.dev == @filesystem
        end

        if @links
          next unless stat_info.nlink == @links
        end

        if @maxdepth || @mindepth
          file_depth = file.split(File::SEPARATOR).length
          current_base_path = [@path].flatten.find {|tpath| file.include?(tpath)}
          path_depth = current_base_path.split(File::SEPARATOR).length

          depth = file_depth - path_depth

          if @maxdepth && (depth > @maxdepth)
            if File.directory?(file)
              unless paths.include?(file) && depth > @maxdepth
                paths << file
              end
            end

            next
          end

          if @mindepth && (depth < @mindepth)
            if File.directory?(file)
              unless paths.include?(file) && depth < @mindepth
                paths << file
              end
            end

            next
          end
        end

        # Add directories back onto the list of paths to search unless
        # they've already been added
        #
        if File.directory?(file) ## stat_info.directory?
          paths << file unless paths.include?(file)
        end

        next unless Dir[glob].include?(file)

        unless @filetest.empty?
          file_test = true

          @filetest.each {|array|
            meth = array[0]
            bool = array[1]

            unless File.send(meth, file) == bool
              file_test = false
              break
            end
          }

          next unless file_test
        end

        if @atime || @ctime || @mtime
          date1 = Date.parse(Time.now.to_s)

          if @atime
            date2 = Date.parse(stat_info.atime.to_s)
            next unless (date1 - date2).numerator == @atime
          end

          if @ctime
            date2 = Date.parse(stat_info.ctime.to_s)
            next unless (date1 - date2).numerator == @ctime
          end

          if @mtime
            date2 = Date.parse(stat_info.mtime.to_s)
            next unless (date1 - date2).numerator == @mtime
          end
        end

        if @ftype
          next unless File.ftype(file) == @ftype
        end

        if @group
          if @group.is_a?(String)
            if File::ALT_SEPARATOR
              begin
                next unless Sys::Admin.get_group(stat_info.gid, :LocalAccount => true).name == @group
              rescue Sys::Admin::Error
                next
              end
            else
              begin
                next unless Sys::Admin.get_group(stat_info.gid).name == @group
              rescue Sys::Admin::Error
                next
              end
            end
          else
            next unless stat_info.gid == @group
          end
        end

        if @inum
          next unless stat_info.ino == @inum
        end

        # Note that only 0644 and 0444 are supported on MS Windows.
        if @perm
          if @perm.is_a?(String)
            octal_perm = sym2oct(@perm)
            next unless stat_info.mode & octal_perm == octal_perm
          else
            next unless sprintf("%o", stat_info.mode & 07777) == sprintf("%o", @perm)
          end
        end

        # Allow plain numbers, or strings for comparison operators.
        if @size
          if @size.is_a?(String)
            regex = /^([><=]+)\s*?(\d+)$/
            match = regex.match(@size)

            if match.nil? || match.captures.include?(nil)
              raise ArgumentError, "invalid size string: '#{@size}'"
            end

            operator = match.captures.first.strip
            number = match.captures.last.strip.to_i

            next unless stat_info.size.send(operator, number)
          else
            next unless stat_info.size == @size
          end
        end

        if @user
          if @user.is_a?(String)
            if File::ALT_SEPARATOR
              begin
                next unless Sys::Admin.get_user(stat_info.uid, :LocalAccount => true).name == @user
              rescue Sys::Admin::Error
                next
              end
            else
              begin
                next unless Sys::Admin.get_user(stat_info.uid).name == @user
              rescue Sys::Admin::Error
                next
              end
            end
          else
            next unless stat_info.uid == @user
          end
        end

        if block_given?
          yield file
        else
          results << file
        end

        @previous = file unless @previous == file
      }
    rescue Errno::EACCES
      next # Skip inaccessible directories
    end
  }

  block_given? ? nil : results
end
mount=(mount_point) click to toggle source

Limits searches to the same file system as the specified mount_point.

# File lib/trop/filefind.rb, line 432
def mount=(mount_point)
  @mount = mount_point
  @filesystem = File.stat(mount_point).dev
end

Private Instance Methods

sym2oct(str) click to toggle source

Converts a symoblic permissions mode into its octal equivalent.

# File lib/trop/filefind.rb, line 469
def sym2oct(str)
  left = {'u' => 0700, 'g' => 0070, 'o' => 0007, 'a' => 0777}
  right = {'r' => 0444, 'w' => 0222, 'x' => 0111}
  regex = /([ugoa]+)([+-=])([rwx]+)/

  cmds = str.split(',')

  perm = 0

  cmds.each do |cmd|
    match = cmd.match(regex)
    raise "Invalid symbolic permissions: '#{str}'" if match.nil?

    who, what, how = match.to_a[1..-1]

    who = who.split(//).inject(0) {|num, b| num |= left[b]; num}
    how = how.split(//).inject(0) {|num, b| num |= right[b]; num}
    mask = who & how

    case what
    when '+'
      perm = perm | mask
    when '-'
      perm = perm & ~mask
    when '='
      perm = mask
    end
  end

  perm
end
validate_and_set_options(options) click to toggle source

This validates that the keys are valid. If they are, it sets the value of that key's corresponding method to the given value. If a key ends with a '?', it's validated as a File method.

# File lib/trop/filefind.rb, line 443
def validate_and_set_options(options)
  options.each do |key, value|
    key = key.to_s.downcase

    if key[-1].chr == '?'
      sym = key.to_sym

      unless File.respond_to?(sym)
        raise ArgumentError, "invalid option '#{key}'"
      end

      @filetest << [sym, value]
    else
      unless VALID_OPTIONS.include?(key)
        raise ArgumentError, "invalid option '#{key}'"
      end

      send("#{key}=", value)
    end
  end
end