require 'date' class File::Find # The version of this package VERSION = '0.1.0' # :stopdoc: VALID_OPTIONS = %w/ atime ctime follow ftype inum group name path size user / # :startdoc: # The starting path(s) for the search. The default is the current directory. # This can be a single path or an array of paths. # attr_accessor :path # The list of options passed to the constructor and/or used by the # File::Find#find method. # attr_accessor :options # 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. Note that the File::Find#find method itself alters the access # times. # attr_accessor :atime # 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. # attr_accessor :ctime # Limits searches to files that belong to a specific group ID. # attr_accessor :group # Controls the behavior of how symlinks are followed. If set to true, then # follows the file pointed to. If false, it considers the symlink itself. # attr_accessor :follow # Limits searches to specific types of files. The possible values here are # those returned by the File.ftype method. # attr_accessor :ftype # Limits search to a file with a specific inode number. Ignored on MS # Windows. # attr_accessor :inum # The name pattern used to limit file searches. The patterns that are legal # for Dir.glob are legal here. # attr_accessor :name # Limits searches to files that match the size, in bytes. # attr_accessor :size # Limits searches to files that belong to a specific user ID. # attr_accessor :user alias pattern name # 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. # def initialize(options = {}) @options = options @atime = nil @ctime = nil @ftype = nil @group = nil @follow = true @inum = nil @name = nil @size = nil @user = nil validate_and_set_options(options) unless options.empty? @path ||= Dir.pwd end # 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. # def find results = [] unless block_given? paths = [@path] catch(:loop) do paths.each{ |path| Dir.foreach(path){ |file| next if file == '.' next if file == '..' file = File.join(path, file) stat_method = @follow ? :lstat : :stat # 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 end # Add directories back onto the list of paths to search unless # they've already been added. # # TODO: Add depth handling. if stat_info.directory? unless paths.include?(file) paths << file next end end if @atime date1 = Date.parse(Time.now.to_s) date2 = Date.parse(stat_info.atime.to_s) next unless (date1 - date2).numerator == @atime end if @ctime date1 = Date.parse(Time.now.to_s) date2 = Date.parse(stat_info.ctime.to_s) next unless (date1 - date2).numerator == @ctime end if @ftype next unless File.ftype(file) == @ftype end if @group next unless stat_info.gid == @group end unless RUBY_PLATFORM.match('mswin') if @inum next unless stat_info.ino == @inum end end if @name glob = File.join(File.dirname(file), @name) next unless Dir[glob].include?(file) end # TODO: Allow more flexible syntax here, e.g. "> 1024". if @size next unless stat_info.size == @size end if @user next unless stat_info.uid == @user end if block_given? yield file else results << file end } } end block_given? ? nil : results end private # This validates that the keys are valid. If they are, it sets the value # of that key's corresponding method to the given value. # def validate_and_set_options(options) options.each do |key, value| key = key.to_s.downcase unless VALID_OPTIONS.include?(key) raise ArgumentError, "invalid option '#{key}'" end send("#{key}=", value) end end end