file-find 0.3.4 → 0.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGES +4 -0
- data/README +75 -73
- data/Rakefile +23 -35
- data/file-find.gemspec +20 -21
- data/lib/file/find.rb +481 -481
- data/test/test_file_find.rb +60 -45
- metadata +39 -17
data/CHANGES
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
== 0.3.5 - 15-Jul-2011
|
2
|
+
* Fixed a bug with the :follow option.
|
3
|
+
* Gemspec, Rakefile and test cleanup.
|
4
|
+
|
1
5
|
== 0.3.4 - 19-Sep-2009
|
2
6
|
* Fixed a packaging bug. Thanks go to Gabriel Horner for the spot.
|
3
7
|
* Added the 'gem' task to the Rakefile for building the gem. Removed the
|
data/README
CHANGED
@@ -1,108 +1,110 @@
|
|
1
1
|
= Description
|
2
|
-
This is a drop-in replacement for the find module currently in the standard
|
3
|
-
library. It is modeled on a typical 'find' command found on most Unix systems.
|
2
|
+
This is a drop-in replacement for the find module currently in the standard
|
3
|
+
library. It is modeled on a typical 'find' command found on most Unix systems.
|
4
4
|
|
5
5
|
= Synopsis
|
6
|
-
|
7
|
-
:pattern => "*.rb",
|
8
|
-
:follow => false,
|
9
|
-
:path => ['/usr/local/lib', '/opt/local/lib']
|
10
|
-
)
|
6
|
+
require 'file/find'
|
11
7
|
|
12
|
-
|
13
|
-
|
14
|
-
|
8
|
+
rule = File::Find.new(
|
9
|
+
:pattern => "*.rb",
|
10
|
+
:follow => false,
|
11
|
+
:path => ['/usr/local/lib', '/opt/local/lib']
|
12
|
+
)
|
13
|
+
|
14
|
+
rule.find{ |f|
|
15
|
+
puts f
|
16
|
+
}
|
15
17
|
|
16
18
|
= Installation
|
17
|
-
|
18
|
-
|
19
|
+
|
20
|
+
gem install file-find
|
19
21
|
|
20
22
|
= Rationale
|
21
|
-
The current find module in the standard library is inadequate. It is, quite
|
22
|
-
frankly, not much more than a plain Dir.glob call. This library provides an
|
23
|
-
interface based on options typically available on your command line 'find'
|
24
|
-
command, thus allowing you much greater control over how you find your files.
|
25
23
|
|
26
|
-
|
27
|
-
|
24
|
+
The current find module in the standard library is inadequate. It is, quite
|
25
|
+
frankly, not much more than a plain Dir.glob call. This library provides an
|
26
|
+
interface based on options typically available on your command line 'find'
|
27
|
+
command, thus allowing you much greater control over how you find your files.
|
28
28
|
|
29
29
|
= Options
|
30
|
-
* atime
|
31
|
-
* ctime
|
32
|
-
* follow
|
33
|
-
* ftype
|
34
|
-
* inum (except Windows)
|
35
|
-
* group (name or id)
|
36
|
-
* maxdepth
|
37
|
-
* mindepth
|
38
|
-
* mount
|
39
|
-
* mtime
|
40
|
-
* name (or 'pattern')
|
41
|
-
* path
|
42
|
-
* perm (except Windows)
|
43
|
-
* prune
|
44
|
-
* size
|
45
|
-
* user (name or id)
|
46
|
-
|
47
|
-
In addition to the above options, FileTest methods such as 'readable?' and
|
48
|
-
'writable?' may be used as keys, with true or false for their values.
|
49
|
-
|
50
|
-
See the RDoc documentation for more details about these options.
|
30
|
+
* atime
|
31
|
+
* ctime
|
32
|
+
* follow
|
33
|
+
* ftype
|
34
|
+
* inum (except Windows)
|
35
|
+
* group (name or id)
|
36
|
+
* maxdepth
|
37
|
+
* mindepth
|
38
|
+
* mount
|
39
|
+
* mtime
|
40
|
+
* name (or 'pattern')
|
41
|
+
* path
|
42
|
+
* perm (except Windows)
|
43
|
+
* prune
|
44
|
+
* size
|
45
|
+
* user (name or id)
|
46
|
+
|
47
|
+
In addition to the above options, FileTest methods such as 'readable?' and
|
48
|
+
'writable?' may be used as keys, with true or false for their values.
|
49
|
+
|
50
|
+
See the RDoc documentation for more details about these options.
|
51
51
|
|
52
52
|
= Future Plans
|
53
|
-
More options will be added as time permits, and requests will definitely be
|
54
|
-
considered. Please log any feature requests on the project page at
|
55
|
-
http://www.rubyforge.org/projects/shards.
|
53
|
+
More options will be added as time permits, and requests will definitely be
|
54
|
+
considered. Please log any feature requests on the project page at
|
55
|
+
http://www.rubyforge.org/projects/shards.
|
56
56
|
|
57
|
-
Some specific things I plan on adding:
|
57
|
+
Some specific things I plan on adding:
|
58
58
|
|
59
|
-
* exec
|
60
|
-
* links
|
61
|
-
* support for :user and :group on MS Windows
|
59
|
+
* exec
|
60
|
+
* links
|
61
|
+
* support for :user and :group on MS Windows
|
62
62
|
|
63
63
|
= Options I won't support
|
64
|
-
Generally speaking, anything that would require mucking around with C code
|
65
|
-
or is just too difficult to implement in a cross platform manner will not be
|
66
|
-
supported. These include the following options:
|
64
|
+
Generally speaking, anything that would require mucking around with C code
|
65
|
+
or is just too difficult to implement in a cross platform manner will not be
|
66
|
+
supported. These include the following options:
|
67
67
|
|
68
|
-
* acl/xattr - Way too difficult to implement in a cross platform manner, and
|
69
|
-
|
68
|
+
* acl/xattr - Way too difficult to implement in a cross platform manner, and
|
69
|
+
a rarely used option in practice.
|
70
70
|
|
71
|
-
* cpio/ncpio - I will not shell out to this or any other 3rd party
|
71
|
+
* cpio/ncpio - I will not shell out to this or any other 3rd party
|
72
|
+
application.
|
72
73
|
|
73
|
-
* ls/print - Use Ruby's builtin printing methods to print as you see fit.
|
74
|
+
* ls/print - Use Ruby's builtin printing methods to print as you see fit.
|
74
75
|
|
75
|
-
* ok - This is not interactive software.
|
76
|
+
* ok - This is not interactive software.
|
76
77
|
|
77
78
|
= Known Issues
|
78
|
-
The 'perm' option does not work on MS Windows, even for its limited subset of
|
79
|
-
permissions, i.e. 664 and 666. This is arguably a bug in Ruby's
|
80
|
-
File::Stat.mode method on MS Windows.
|
79
|
+
The 'perm' option does not work on MS Windows, even for its limited subset of
|
80
|
+
permissions, i.e. 664 and 666. This is arguably a bug in Ruby's
|
81
|
+
File::Stat.mode method on MS Windows.
|
81
82
|
|
82
|
-
The 'user' and 'group' options are not currently supported on MS Windows.
|
83
|
-
This can be supported, but will require changes in the win32-file and
|
84
|
-
win32-file-stat libraries (which would then become dependencies).
|
83
|
+
The 'user' and 'group' options are not currently supported on MS Windows.
|
84
|
+
This can be supported, but will require changes in the win32-file and
|
85
|
+
win32-file-stat libraries (which would then become dependencies).
|
85
86
|
|
86
|
-
There are 3 test failures with JRuby, all related to the 'perm' option. I
|
87
|
-
have not been able to reduce them to a simple test case and discern the
|
88
|
-
exact cause of the failures, though I suspect a bug in the JRuby
|
89
|
-
implementation of File.chmod.
|
87
|
+
There are 3 test failures with JRuby, all related to the 'perm' option. I
|
88
|
+
have not been able to reduce them to a simple test case and discern the
|
89
|
+
exact cause of the failures, though I suspect a bug in the JRuby
|
90
|
+
implementation of File.chmod.
|
90
91
|
|
91
92
|
= Bugs
|
92
|
-
None that I'm aware of
|
93
|
-
|
93
|
+
None that I'm aware of beyond the ones mentioned in the Known Issues. Please
|
94
|
+
log any bug reports on the project page at
|
95
|
+
http://www.rubyforge.org/projects/shards.
|
94
96
|
|
95
97
|
= Acknowledgements
|
96
|
-
* Richard Clamp's File::Find::Rule Perl module for additional ideas and
|
97
|
-
|
98
|
-
* Bill Kleb for ideas regarding name, group and perm enhancements.
|
99
|
-
* Hal Fulton for his implementation of symbolic permissions.
|
98
|
+
* Richard Clamp's File::Find::Rule Perl module for additional ideas and
|
99
|
+
inspiration.
|
100
|
+
* Bill Kleb for ideas regarding name, group and perm enhancements.
|
101
|
+
* Hal Fulton for his implementation of symbolic permissions.
|
100
102
|
|
101
103
|
= License
|
102
|
-
Artistic 2.0
|
104
|
+
Artistic 2.0
|
103
105
|
|
104
106
|
= Copyright
|
105
|
-
(C) 2007-
|
107
|
+
(C) 2007-2011, Daniel J. Berger, All Rights Reserved
|
106
108
|
|
107
109
|
= Author
|
108
|
-
Daniel J. Berger
|
110
|
+
Daniel J. Berger
|
data/Rakefile
CHANGED
@@ -1,46 +1,34 @@
|
|
1
1
|
require 'rake'
|
2
|
+
require 'rake/clean'
|
2
3
|
require 'rake/testtask'
|
3
4
|
|
4
|
-
|
5
|
-
task :clean do
|
6
|
-
rm_rf '.test-result' if File.exists?('.test-result')
|
5
|
+
CLEAN.include("**/*.gem", "**/*.rbc", "**/link*")
|
7
6
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
end
|
14
|
-
}
|
15
|
-
end
|
16
|
-
|
17
|
-
desc "Install the file-find library (non-gem)"
|
18
|
-
task :install do
|
19
|
-
dest = File.join(Config::CONFIG['sitelibdir'], 'file')
|
20
|
-
Dir.mkdir(dest) unless File.exists? dest
|
21
|
-
cp 'lib/file/find.rb', dest, :verbose => true
|
22
|
-
end
|
23
|
-
|
24
|
-
desc "Install the file-find library as a gem"
|
25
|
-
task :install_gem do
|
26
|
-
ruby 'file-find.gemspec'
|
27
|
-
file = Dir["*.gem"].first
|
28
|
-
sh "gem install #{file}"
|
29
|
-
end
|
30
|
-
|
31
|
-
desc 'Create a gem'
|
32
|
-
task :gem do
|
33
|
-
spec = eval(IO.read('file-find.gemspec'))
|
34
|
-
if RUBY_PLATFORM.match('java')
|
7
|
+
namespace :gem do
|
8
|
+
desc 'Create the file-find gem'
|
9
|
+
task :create => [:clean] do
|
10
|
+
spec = eval(IO.read('file-find.gemspec'))
|
11
|
+
if RUBY_PLATFORM.match('java')
|
35
12
|
spec.platform = Gem::Platform::CURRENT
|
36
|
-
|
13
|
+
else
|
37
14
|
spec.add_dependency('sys-admin', '>= 1.5.2')
|
38
|
-
|
15
|
+
end
|
39
16
|
|
40
|
-
|
17
|
+
Gem::Builder.new(spec).build
|
18
|
+
end
|
19
|
+
|
20
|
+
desc "Install the file-find gem"
|
21
|
+
task :install => [:create] do
|
22
|
+
ruby 'file-find.gemspec'
|
23
|
+
file = Dir["*.gem"].first
|
24
|
+
sh "gem install #{file}"
|
25
|
+
end
|
41
26
|
end
|
42
27
|
|
43
28
|
Rake::TestTask.new do |t|
|
44
|
-
|
45
|
-
|
29
|
+
task :test => 'clean'
|
30
|
+
t.warning = true
|
31
|
+
t.verbose = true
|
46
32
|
end
|
33
|
+
|
34
|
+
task :default => :test
|
data/file-find.gemspec
CHANGED
@@ -1,27 +1,26 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
|
3
|
-
Gem::Specification.new do |
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
gem.has_rdoc = true
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.name = 'file-find'
|
5
|
+
spec.version = '0.3.5'
|
6
|
+
spec.author = 'Daniel Berger'
|
7
|
+
spec.license = 'Artistic 2.0'
|
8
|
+
spec.summary = 'A better way to find files'
|
9
|
+
spec.email = 'djberg96@gmail.com'
|
10
|
+
spec.homepage = 'http://www.rubyforge.org/projects/shards'
|
11
|
+
spec.platform = Gem::Platform::RUBY
|
12
|
+
spec.files = Dir['**/*'].reject{ |f| f.include?('git') }
|
13
|
+
spec.test_file = 'test/test_file_find.rb'
|
15
14
|
|
16
|
-
|
17
|
-
|
15
|
+
spec.rubyforge_project = 'shards'
|
16
|
+
spec.extra_rdoc_files = ['README', 'CHANGES', 'MANIFEST']
|
18
17
|
|
19
|
-
|
18
|
+
spec.add_development_dependency('test-unit', '>= 2.1.1')
|
20
19
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
20
|
+
spec.description = <<-EOF
|
21
|
+
The file-find library provides a better, more object oriented approach
|
22
|
+
to finding files. It allows you to find files based on a variety of
|
23
|
+
properties, such as access time, size, owner, etc. You can also limit
|
24
|
+
directory depth.
|
25
|
+
EOF
|
27
26
|
end
|
data/lib/file/find.rb
CHANGED
@@ -4,501 +4,501 @@ require 'rbconfig'
|
|
4
4
|
# For alternate implementations of Ruby, such as JRuby, that cannot
|
5
5
|
# build C extensions fall back to the Etc module.
|
6
6
|
begin
|
7
|
-
|
7
|
+
require 'sys/admin'
|
8
8
|
rescue LoadError
|
9
|
-
|
9
|
+
require 'etc'
|
10
10
|
end
|
11
11
|
|
12
12
|
class File::Find
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
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
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
13
|
+
# The version of the file-find library
|
14
|
+
VERSION = '0.3.5'
|
15
|
+
|
16
|
+
# :stopdoc:
|
17
|
+
VALID_OPTIONS = %w/
|
18
|
+
atime
|
19
|
+
ctime
|
20
|
+
follow
|
21
|
+
ftype
|
22
|
+
inum
|
23
|
+
group
|
24
|
+
links
|
25
|
+
maxdepth
|
26
|
+
mindepth
|
27
|
+
mount
|
28
|
+
mtime
|
29
|
+
name
|
30
|
+
pattern
|
31
|
+
path
|
32
|
+
perm
|
33
|
+
prune
|
34
|
+
size
|
35
|
+
user
|
36
|
+
/
|
37
|
+
# :startdoc:
|
38
|
+
|
39
|
+
# The starting path(s) for the search. The default is the current directory.
|
40
|
+
# This can be a single path or an array of paths.
|
41
|
+
#
|
42
|
+
attr_accessor :path
|
43
|
+
|
44
|
+
# The list of options passed to the constructor and/or used by the
|
45
|
+
# File::Find#find method.
|
46
|
+
#
|
47
|
+
attr_accessor :options
|
48
|
+
|
49
|
+
# Limits searches by file access time, where the value you supply is the
|
50
|
+
# number of days back from the time that the File::Find#find method was
|
51
|
+
# called.
|
52
|
+
#
|
53
|
+
attr_accessor :atime
|
54
|
+
|
55
|
+
# Limits searches by file change time, where the value you supply is the
|
56
|
+
# number of days back from the time that the File::Find#find method was
|
57
|
+
# called.
|
58
|
+
#
|
59
|
+
attr_accessor :ctime
|
60
|
+
|
61
|
+
# Limits searches to files that belong to a specific group, where the
|
62
|
+
# group can be either a group name or ID.
|
63
|
+
#
|
64
|
+
# Not currently supported on MS Windows.
|
65
|
+
#
|
66
|
+
attr_accessor :group
|
67
|
+
|
68
|
+
# An array of two element arrays for storing FileTest methods and their
|
69
|
+
# boolean value.
|
70
|
+
#
|
71
|
+
attr_accessor :filetest
|
72
|
+
|
73
|
+
# Controls the behavior of how symlinks are followed. If set to true (the
|
74
|
+
# default), then follows the file pointed to. If false, it considers the
|
75
|
+
# symlink itself.
|
76
|
+
#
|
77
|
+
attr_accessor :follow
|
78
|
+
|
79
|
+
# Limits searches to specific types of files. The possible values here are
|
80
|
+
# those returned by the File.ftype method.
|
81
|
+
#
|
82
|
+
attr_accessor :ftype
|
83
|
+
|
84
|
+
# Limits search to a file with a specific inode number. Ignored on MS
|
85
|
+
# Windows.
|
86
|
+
#
|
87
|
+
attr_accessor :inum
|
88
|
+
|
89
|
+
# Limits search to files with the specified number of links.
|
90
|
+
#
|
91
|
+
attr_accessor :links
|
92
|
+
|
93
|
+
# Limits search to a maximum depth into the tree relative to the starting
|
94
|
+
# search directory.
|
95
|
+
#
|
96
|
+
attr_accessor :maxdepth
|
97
|
+
|
98
|
+
# Limits searches to a minimum depth into the tree relative to the starting
|
99
|
+
# search directory.
|
100
|
+
#
|
101
|
+
attr_accessor :mindepth
|
102
|
+
|
103
|
+
# Limits searches to the same filesystem as the specified directory. For
|
104
|
+
# Windows users, this refers to the volume.
|
105
|
+
#
|
106
|
+
attr_reader :mount
|
107
|
+
|
108
|
+
# Limits searches by file modification time, where the value you supply is
|
109
|
+
# the number of days back from the time that the File::Find#find method was
|
110
|
+
# called.
|
111
|
+
#
|
112
|
+
attr_accessor :mtime
|
113
|
+
|
114
|
+
# The name pattern used to limit file searches. The patterns that are legal
|
115
|
+
# for Dir.glob are legal here. The default is '*', i.e. everything.
|
116
|
+
#
|
117
|
+
attr_accessor :name
|
118
|
+
|
119
|
+
# Limits searches to files which have permissions that match the octal
|
120
|
+
# value that you provide. For purposes of this comparison, only the user,
|
121
|
+
# group, and world settings are used. Do not use a leading 0 in the values
|
122
|
+
# that you supply, e.g. use 755 not 0755.
|
123
|
+
#
|
124
|
+
# You may optionally use symbolic permissions, e.g. "g+rw", "u=rwx", etc.
|
125
|
+
#
|
126
|
+
# Not currently supported on MS Windows.
|
127
|
+
#
|
128
|
+
attr_accessor :perm
|
129
|
+
|
130
|
+
# Skips files or directories that match the string provided as an argument.
|
131
|
+
#
|
132
|
+
attr_accessor :prune
|
133
|
+
|
134
|
+
# If the value passed is an integer, this option limits searches to files
|
135
|
+
# that match the size, in bytes, exactly. If a string is passed, you can
|
136
|
+
# use the standard comparable operators to match files, e.g. ">= 200" would
|
137
|
+
# limit searches to files greater than or equal to 200 bytes.
|
138
|
+
#
|
139
|
+
attr_accessor :size
|
140
|
+
|
141
|
+
# Limits searches to files that belong to a specific user, where the user
|
142
|
+
# can be either a user name or an ID.
|
143
|
+
#
|
144
|
+
# Not currently supported on MS Windows.
|
145
|
+
#
|
146
|
+
attr_accessor :user
|
147
|
+
|
148
|
+
# The file that matched previously in the current search.
|
149
|
+
#
|
150
|
+
attr_reader :previous
|
151
|
+
|
152
|
+
alias pattern name
|
153
|
+
alias pattern= name=
|
154
|
+
|
155
|
+
# Creates and returns a new File::Find object. The options set for this
|
156
|
+
# object serve as the rules for determining what files the File::Find#find
|
157
|
+
# method will search for.
|
158
|
+
#
|
159
|
+
# In addition to the standard list of valid options, you may also use
|
160
|
+
# FileTest methods as options, setting their value to true or false.
|
161
|
+
#
|
162
|
+
# Example:
|
163
|
+
#
|
164
|
+
# rule = File::Find.new(
|
165
|
+
# :name => "*.rb",
|
166
|
+
# :follow => false,
|
167
|
+
# :path => ['/usr/local/lib', '/opt/local/lib'],
|
168
|
+
# :readable? => true
|
169
|
+
# )
|
170
|
+
#
|
171
|
+
def initialize(options = {})
|
172
|
+
@options = options
|
173
|
+
|
174
|
+
@atime = nil
|
175
|
+
@ctime = nil
|
176
|
+
@ftype = nil
|
177
|
+
@group = nil
|
178
|
+
@follow = true
|
179
|
+
@inum = nil
|
180
|
+
@links = nil
|
181
|
+
@mount = nil
|
182
|
+
@mtime = nil
|
183
|
+
@perm = nil
|
184
|
+
@prune = nil
|
185
|
+
@size = nil
|
186
|
+
@user = nil
|
187
|
+
|
188
|
+
@previous = nil
|
189
|
+
@maxdepth = nil
|
190
|
+
@mindepth = nil
|
191
|
+
@filetest = []
|
192
|
+
|
193
|
+
validate_and_set_options(options) unless options.empty?
|
194
|
+
|
195
|
+
@filesystem = File.stat(@mount).dev if @mount
|
196
|
+
|
197
|
+
@path ||= Dir.pwd
|
198
|
+
@name ||= '*'
|
199
|
+
end
|
200
|
+
|
201
|
+
# Executes the find based on the rules you set for the File::Find object.
|
202
|
+
# In block form, yields each file in turn that matches the specified rules.
|
203
|
+
# In non-block form it will return an array of matches instead.
|
204
|
+
#
|
205
|
+
# Example:
|
206
|
+
#
|
207
|
+
# rule = File::Find.new(
|
208
|
+
# :name => "*.rb",
|
209
|
+
# :follow => false,
|
210
|
+
# :path => ['/usr/local/lib', '/opt/local/lib']
|
211
|
+
# )
|
212
|
+
#
|
213
|
+
# rule.find{ |f|
|
214
|
+
# puts f
|
215
|
+
# }
|
216
|
+
#
|
217
|
+
def find
|
218
|
+
results = [] unless block_given?
|
219
|
+
paths = @path.is_a?(String) ? [@path] : @path # Ruby 1.9.x compatibility
|
220
|
+
|
221
|
+
if @prune
|
222
|
+
prune_regex = Regexp.new(@prune)
|
223
|
+
else
|
224
|
+
prune_regex = nil
|
225
|
+
end
|
226
|
+
|
227
|
+
paths.each{ |path|
|
228
|
+
begin
|
229
|
+
Dir.foreach(path){ |file|
|
230
|
+
next if file == '.'
|
231
|
+
next if file == '..'
|
232
|
+
|
233
|
+
if prune_regex
|
234
|
+
next if prune_regex.match(file)
|
235
|
+
end
|
236
|
+
|
237
|
+
orig = file.dup
|
238
|
+
file = File.join(path, file)
|
239
|
+
|
240
|
+
stat_method = @follow ? :stat : :lstat
|
241
|
+
|
242
|
+
# Skip files we cannot access, stale links, etc.
|
243
|
+
begin
|
244
|
+
stat_info = File.send(stat_method, file)
|
245
|
+
rescue Errno::ENOENT, Errno::EACCES
|
246
|
+
next
|
247
|
+
rescue Errno::ELOOP
|
248
|
+
stat_method = :lstat # Handle recursive symlinks
|
249
|
+
retry if stat_method.to_s != 'lstat'
|
250
|
+
end
|
251
|
+
|
252
|
+
glob = File.join(File.dirname(file), @name)
|
253
|
+
|
254
|
+
# Dir[] doesn't like backslashes
|
255
|
+
if File::ALT_SEPARATOR
|
256
|
+
file.tr!(File::ALT_SEPARATOR, File::SEPARATOR)
|
257
|
+
glob.tr!(File::ALT_SEPARATOR, File::SEPARATOR)
|
258
|
+
end
|
259
|
+
|
260
|
+
if @mount
|
261
|
+
next unless stat_info.dev == @filesystem
|
262
|
+
end
|
263
|
+
|
264
|
+
if @links
|
265
|
+
next unless stat_info.nlink == @links
|
266
|
+
end
|
267
|
+
|
268
|
+
if @maxdepth || @mindepth
|
269
|
+
file_depth = file.split(File::SEPARATOR).length
|
270
|
+
path_depth = @path.split(File::SEPARATOR).length
|
271
|
+
depth = file_depth - path_depth
|
272
|
+
|
273
|
+
if @maxdepth && (depth > @maxdepth)
|
274
|
+
if File.directory?(file)
|
275
|
+
unless paths.include?(file) && depth > @maxdepth
|
276
|
+
paths << file
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
next
|
281
|
+
end
|
282
|
+
|
283
|
+
if @mindepth && (depth < @mindepth)
|
284
|
+
if File.directory?(file)
|
285
|
+
unless paths.include?(file) && depth < @mindepth
|
286
|
+
paths << file
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
next
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
# Add directories back onto the list of paths to search unless
|
295
|
+
# they've already been added
|
296
|
+
#
|
297
|
+
if stat_info.directory?
|
298
|
+
paths << file unless paths.include?(file)
|
299
|
+
end
|
300
|
+
|
301
|
+
next unless Dir[glob].include?(file)
|
302
|
+
|
303
|
+
unless @filetest.empty?
|
304
|
+
file_test = true
|
226
305
|
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
end
|
236
|
-
|
237
|
-
orig = file.dup
|
238
|
-
file = File.join(path, file)
|
239
|
-
|
240
|
-
stat_method = @follow ? :lstat : :stat
|
241
|
-
|
242
|
-
# Skip files we cannot access, stale links, etc.
|
243
|
-
begin
|
244
|
-
stat_info = File.send(stat_method, file)
|
245
|
-
rescue Errno::ENOENT, Errno::EACCES
|
246
|
-
next
|
247
|
-
rescue Errno::ELOOP
|
248
|
-
stat_method = :lstat # Handle recursive symlinks
|
249
|
-
retry if stat_method.to_s != 'lstat'
|
250
|
-
end
|
251
|
-
|
252
|
-
glob = File.join(File.dirname(file), @name)
|
253
|
-
|
254
|
-
# Dir[] doesn't like backslashes
|
255
|
-
if File::ALT_SEPARATOR
|
256
|
-
file.tr!(File::ALT_SEPARATOR, File::SEPARATOR)
|
257
|
-
glob.tr!(File::ALT_SEPARATOR, File::SEPARATOR)
|
258
|
-
end
|
259
|
-
|
260
|
-
if @mount
|
261
|
-
next unless stat_info.dev == @filesystem
|
262
|
-
end
|
263
|
-
|
264
|
-
if @links
|
265
|
-
next unless stat_info.nlink == @links
|
266
|
-
end
|
267
|
-
|
268
|
-
if @maxdepth || @mindepth
|
269
|
-
file_depth = file.split(File::SEPARATOR).length
|
270
|
-
path_depth = @path.split(File::SEPARATOR).length
|
271
|
-
depth = file_depth - path_depth
|
272
|
-
|
273
|
-
if @maxdepth && (depth > @maxdepth)
|
274
|
-
if File.directory?(file)
|
275
|
-
unless paths.include?(file) && depth > @maxdepth
|
276
|
-
paths << file
|
277
|
-
end
|
278
|
-
end
|
279
|
-
|
280
|
-
next
|
281
|
-
end
|
282
|
-
|
283
|
-
if @mindepth && (depth < @mindepth)
|
284
|
-
if File.directory?(file)
|
285
|
-
unless paths.include?(file) && depth < @mindepth
|
286
|
-
paths << file
|
287
|
-
end
|
288
|
-
end
|
289
|
-
|
290
|
-
next
|
291
|
-
end
|
292
|
-
end
|
293
|
-
|
294
|
-
# Add directories back onto the list of paths to search unless
|
295
|
-
# they've already been added
|
296
|
-
#
|
297
|
-
if stat_info.directory?
|
298
|
-
paths << file unless paths.include?(file)
|
299
|
-
end
|
300
|
-
|
301
|
-
next unless Dir[glob].include?(file)
|
302
|
-
|
303
|
-
unless @filetest.empty?
|
304
|
-
file_test = true
|
305
|
-
|
306
|
-
@filetest.each{ |array|
|
307
|
-
meth = array[0]
|
308
|
-
bool = array[1]
|
309
|
-
|
310
|
-
unless File.send(meth, file) == bool
|
311
|
-
file_test = false
|
312
|
-
break
|
313
|
-
end
|
314
|
-
}
|
315
|
-
|
316
|
-
next unless file_test
|
317
|
-
end
|
318
|
-
|
319
|
-
if @atime || @ctime || @mtime
|
320
|
-
date1 = Date.parse(Time.now.to_s)
|
321
|
-
|
322
|
-
if @atime
|
323
|
-
date2 = Date.parse(stat_info.atime.to_s)
|
324
|
-
next unless (date1 - date2).numerator == @atime
|
325
|
-
end
|
326
|
-
|
327
|
-
if @ctime
|
328
|
-
date2 = Date.parse(stat_info.ctime.to_s)
|
329
|
-
next unless (date1 - date2).numerator == @ctime
|
330
|
-
end
|
331
|
-
|
332
|
-
if @mtime
|
333
|
-
date2 = Date.parse(stat_info.mtime.to_s)
|
334
|
-
next unless (date1 - date2).numerator == @mtime
|
335
|
-
end
|
336
|
-
end
|
337
|
-
|
338
|
-
if @ftype
|
339
|
-
next unless File.ftype(file) == @ftype
|
340
|
-
end
|
341
|
-
|
342
|
-
if @group
|
343
|
-
if @group.is_a?(String)
|
344
|
-
next unless get_group(stat_info.gid).name == @group
|
345
|
-
else
|
346
|
-
next unless stat_info.gid == @group
|
347
|
-
end
|
348
|
-
end
|
349
|
-
|
350
|
-
unless Config::CONFIG['host_os'] =~ /windows|mswin/i
|
351
|
-
if @inum
|
352
|
-
next unless stat_info.ino == @inum
|
353
|
-
end
|
354
|
-
end
|
355
|
-
|
356
|
-
# This currently doesn't work on MS Windows, even in limited
|
357
|
-
# fashion for 0666 and 0664, because File.stat.mode doesn't
|
358
|
-
# return the proper value.
|
359
|
-
#
|
360
|
-
if @perm
|
361
|
-
if @perm.is_a?(String)
|
362
|
-
octal_perm = sym2oct(@perm)
|
363
|
-
next unless stat_info.mode & octal_perm == octal_perm
|
364
|
-
else
|
365
|
-
next unless sprintf("%o", stat_info.mode & 07777) == @perm.to_s
|
366
|
-
end
|
367
|
-
end
|
368
|
-
|
369
|
-
# Allow plain numbers, or strings for comparison operators.
|
370
|
-
if @size
|
371
|
-
if @size.is_a?(String)
|
372
|
-
regex = /^([><=]+)\s*?(\d+)$/
|
373
|
-
match = regex.match(@size)
|
374
|
-
|
375
|
-
if match.nil? || match.captures.include?(nil)
|
376
|
-
raise ArgumentError, "invalid size string: '#{@size}'"
|
377
|
-
end
|
378
|
-
|
379
|
-
operator = match.captures.first.strip
|
380
|
-
number = match.captures.last.strip.to_i
|
381
|
-
|
382
|
-
next unless stat_info.size.send(operator, number)
|
383
|
-
else
|
384
|
-
next unless stat_info.size == @size
|
385
|
-
end
|
386
|
-
end
|
387
|
-
|
388
|
-
if @user
|
389
|
-
if @user.is_a?(String)
|
390
|
-
next unless get_user(stat_info.uid).name == @user
|
391
|
-
else
|
392
|
-
next unless stat_info.uid == @user
|
393
|
-
end
|
394
|
-
end
|
395
|
-
|
396
|
-
if block_given?
|
397
|
-
yield file
|
398
|
-
else
|
399
|
-
results << file
|
400
|
-
end
|
401
|
-
|
402
|
-
@previous = file unless @previous == file
|
306
|
+
@filetest.each{ |array|
|
307
|
+
meth = array[0]
|
308
|
+
bool = array[1]
|
309
|
+
|
310
|
+
unless File.send(meth, file) == bool
|
311
|
+
file_test = false
|
312
|
+
break
|
313
|
+
end
|
403
314
|
}
|
404
|
-
|
405
|
-
next
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
def mount=(mount_point)
|
415
|
-
@mount = mount_point
|
416
|
-
@filesystem = File.stat(mount_point).dev
|
417
|
-
end
|
418
|
-
|
419
|
-
private
|
420
|
-
|
421
|
-
# This validates that the keys are valid. If they are, it sets the value
|
422
|
-
# of that key's corresponding method to the given value. If a key ends
|
423
|
-
# with a '?', it's validated as a File method.
|
424
|
-
#
|
425
|
-
def validate_and_set_options(options)
|
426
|
-
options.each do |key, value|
|
427
|
-
key = key.to_s.downcase
|
428
|
-
|
429
|
-
if key[-1].chr == '?'
|
430
|
-
sym = key.to_sym
|
431
|
-
|
432
|
-
unless File.respond_to?(sym)
|
433
|
-
raise ArgumentError, "invalid option '#{key}'"
|
315
|
+
|
316
|
+
next unless file_test
|
317
|
+
end
|
318
|
+
|
319
|
+
if @atime || @ctime || @mtime
|
320
|
+
date1 = Date.parse(Time.now.to_s)
|
321
|
+
|
322
|
+
if @atime
|
323
|
+
date2 = Date.parse(stat_info.atime.to_s)
|
324
|
+
next unless (date1 - date2).numerator == @atime
|
434
325
|
end
|
435
326
|
|
436
|
-
@
|
437
|
-
|
438
|
-
|
439
|
-
raise ArgumentError, "invalid option '#{key}'"
|
327
|
+
if @ctime
|
328
|
+
date2 = Date.parse(stat_info.ctime.to_s)
|
329
|
+
next unless (date1 - date2).numerator == @ctime
|
440
330
|
end
|
441
331
|
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
332
|
+
if @mtime
|
333
|
+
date2 = Date.parse(stat_info.mtime.to_s)
|
334
|
+
next unless (date1 - date2).numerator == @mtime
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
if @ftype
|
339
|
+
next unless File.ftype(file) == @ftype
|
340
|
+
end
|
341
|
+
|
342
|
+
if @group
|
343
|
+
if @group.is_a?(String)
|
344
|
+
next unless get_group(stat_info.gid).name == @group
|
345
|
+
else
|
346
|
+
next unless stat_info.gid == @group
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
unless Config::CONFIG['host_os'] =~ /windows|mswin/i
|
351
|
+
if @inum
|
352
|
+
next unless stat_info.ino == @inum
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
# This currently doesn't work on MS Windows, even in limited
|
357
|
+
# fashion for 0666 and 0664, because File.stat.mode doesn't
|
358
|
+
# return the proper value.
|
359
|
+
#
|
360
|
+
if @perm
|
361
|
+
if @perm.is_a?(String)
|
362
|
+
octal_perm = sym2oct(@perm)
|
363
|
+
next unless stat_info.mode & octal_perm == octal_perm
|
364
|
+
else
|
365
|
+
next unless sprintf("%o", stat_info.mode & 07777) == @perm.to_s
|
366
|
+
end
|
367
|
+
end
|
368
|
+
|
369
|
+
# Allow plain numbers, or strings for comparison operators.
|
370
|
+
if @size
|
371
|
+
if @size.is_a?(String)
|
372
|
+
regex = /^([><=]+)\s*?(\d+)$/
|
373
|
+
match = regex.match(@size)
|
374
|
+
|
375
|
+
if match.nil? || match.captures.include?(nil)
|
376
|
+
raise ArgumentError, "invalid size string: '#{@size}'"
|
377
|
+
end
|
378
|
+
|
379
|
+
operator = match.captures.first.strip
|
380
|
+
number = match.captures.last.strip.to_i
|
381
|
+
|
382
|
+
next unless stat_info.size.send(operator, number)
|
383
|
+
else
|
384
|
+
next unless stat_info.size == @size
|
385
|
+
end
|
386
|
+
end
|
387
|
+
|
388
|
+
if @user
|
389
|
+
if @user.is_a?(String)
|
390
|
+
next unless get_user(stat_info.uid).name == @user
|
391
|
+
else
|
392
|
+
next unless stat_info.uid == @user
|
393
|
+
end
|
394
|
+
end
|
395
|
+
|
396
|
+
if block_given?
|
397
|
+
yield file
|
398
|
+
else
|
399
|
+
results << file
|
400
|
+
end
|
401
|
+
|
402
|
+
@previous = file unless @previous == file
|
403
|
+
}
|
404
|
+
rescue Errno::EACCES
|
405
|
+
next # Skip inaccessible directories
|
478
406
|
end
|
407
|
+
}
|
408
|
+
|
409
|
+
block_given? ? nil : results
|
410
|
+
end
|
411
|
+
|
412
|
+
# Limits searches to the same file system as the specified +mount_point+.
|
413
|
+
#
|
414
|
+
def mount=(mount_point)
|
415
|
+
@mount = mount_point
|
416
|
+
@filesystem = File.stat(mount_point).dev
|
417
|
+
end
|
418
|
+
|
419
|
+
private
|
479
420
|
|
480
|
-
|
481
|
-
|
421
|
+
# This validates that the keys are valid. If they are, it sets the value
|
422
|
+
# of that key's corresponding method to the given value. If a key ends
|
423
|
+
# with a '?', it's validated as a File method.
|
424
|
+
#
|
425
|
+
def validate_and_set_options(options)
|
426
|
+
options.each do |key, value|
|
427
|
+
key = key.to_s.downcase
|
482
428
|
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
429
|
+
if key[-1].chr == '?'
|
430
|
+
sym = key.to_sym
|
431
|
+
|
432
|
+
unless File.respond_to?(sym)
|
433
|
+
raise ArgumentError, "invalid option '#{key}'"
|
434
|
+
end
|
435
|
+
|
436
|
+
@filetest << [sym, value]
|
489
437
|
else
|
490
|
-
|
438
|
+
unless VALID_OPTIONS.include?(key)
|
439
|
+
raise ArgumentError, "invalid option '#{key}'"
|
440
|
+
end
|
441
|
+
|
442
|
+
send("#{key}=", value)
|
491
443
|
end
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
444
|
+
end
|
445
|
+
end
|
446
|
+
|
447
|
+
# Converts a symoblic permissions mode into its octal equivalent.
|
448
|
+
#--
|
449
|
+
# Taken almost entirely from ruby-talk: 96956 (Hal Fulton).
|
450
|
+
#
|
451
|
+
def sym2oct(str)
|
452
|
+
left = {'u' => 0700, 'g' => 0070, 'o' => 0007, 'a' => 0777}
|
453
|
+
right = {'r' => 0444, 'w' => 0222, 'x' => 0111}
|
454
|
+
regex = /([ugoa]+)([+-=])([rwx]+)/
|
455
|
+
|
456
|
+
cmds = str.split(',')
|
457
|
+
|
458
|
+
perm = 0
|
459
|
+
|
460
|
+
cmds.each do |cmd|
|
461
|
+
match = cmd.match(regex)
|
462
|
+
raise "Invalid symbolic permissions: '#{str}'" if match.nil?
|
463
|
+
|
464
|
+
junk, who, what, how = match.to_a
|
465
|
+
|
466
|
+
who = who.split(//).inject(who_num=0){ |num,b| num |= left[b]; num }
|
467
|
+
how = how.split(//).inject(how_num=0){ |num,b| num |= right[b]; num }
|
468
|
+
mask = who & how
|
469
|
+
|
470
|
+
case what
|
471
|
+
when '+'
|
472
|
+
perm = perm | mask
|
473
|
+
when '-'
|
474
|
+
perm = perm & ~mask
|
475
|
+
when '='
|
476
|
+
perm = mask
|
502
477
|
end
|
503
|
-
|
478
|
+
end
|
479
|
+
|
480
|
+
perm
|
481
|
+
end
|
482
|
+
|
483
|
+
# Returns the group object based on the group id. Implemented for the
|
484
|
+
# sake of platforms that cannot build extensions, such as JRuby.
|
485
|
+
#
|
486
|
+
def get_group(gid)
|
487
|
+
if defined? Sys::Admin
|
488
|
+
Sys::Admin.get_group(gid)
|
489
|
+
else
|
490
|
+
Etc.getgrgid(gid)
|
491
|
+
end
|
492
|
+
end
|
493
|
+
|
494
|
+
# Returns the user object based on the group id. Implemented for the
|
495
|
+
# sake of platforms that cannot build extensions, such as JRuby.
|
496
|
+
#
|
497
|
+
def get_user(uid)
|
498
|
+
if defined? Sys::Admin
|
499
|
+
Sys::Admin.get_user(uid)
|
500
|
+
else
|
501
|
+
Etc.getpwuid(uid)
|
502
|
+
end
|
503
|
+
end
|
504
504
|
end
|