file-find 0.3.3 → 0.3.4

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 CHANGED
@@ -1,3 +1,9 @@
1
+ == 0.3.4 - 19-Sep-2009
2
+ * Fixed a packaging bug. Thanks go to Gabriel Horner for the spot.
3
+ * Added the 'gem' task to the Rakefile for building the gem. Removed the
4
+ gem builder code that was in the gemspec itself.
5
+ * Updated the dependency for sys-admin to 1.5.2.
6
+
1
7
  == 0.3.3 - 3-Aug-2009
2
8
  * Now compatible with Ruby 1.9.x.
3
9
  * Added support for the :links option
data/Rakefile ADDED
@@ -0,0 +1,46 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+
4
+ desc "Cleanup .test-result files if present"
5
+ task :clean do
6
+ rm_rf '.test-result' if File.exists?('.test-result')
7
+
8
+ Dir.foreach(Dir.pwd){ |file|
9
+ if File.directory?(file)
10
+ Dir.chdir(file){
11
+ rm_rf '.test-result' if File.exists?('.test-result')
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')
35
+ spec.platform = Gem::Platform::CURRENT
36
+ else
37
+ spec.add_dependency('sys-admin', '>= 1.5.2')
38
+ end
39
+
40
+ Gem::Builder.new(spec).build
41
+ end
42
+
43
+ Rake::TestTask.new do |t|
44
+ t.warning = true
45
+ t.verbose = true
46
+ end
data/file-find.gemspec ADDED
@@ -0,0 +1,27 @@
1
+ require 'rubygems'
2
+
3
+ Gem::Specification.new do |gem|
4
+ gem.name = 'file-find'
5
+ gem.version = '0.3.4'
6
+ gem.author = 'Daniel Berger'
7
+ gem.license = 'Artistic 2.0'
8
+ gem.summary = 'A better way to find files'
9
+ gem.email = 'djberg96@gmail.com'
10
+ gem.homepage = 'http://www.rubyforge.org/projects/shards'
11
+ gem.platform = Gem::Platform::RUBY
12
+ gem.files = Dir['**/*'].reject{ |f| f.include?('CVS') }
13
+ gem.test_file = 'test/test_file_find.rb'
14
+ gem.has_rdoc = true
15
+
16
+ gem.rubyforge_project = 'shards'
17
+ gem.extra_rdoc_files = ['README', 'CHANGES', 'MANIFEST']
18
+
19
+ gem.add_development_dependency('test-unit', '>= 2.0.3')
20
+
21
+ gem.description = <<-EOF
22
+ The file-find library provides a better, more object oriented approach
23
+ to finding files. It allows you to find files based on a variety of
24
+ properties, such as access time, size, owner, etc. You can also limit
25
+ directory depth.
26
+ EOF
27
+ end
data/lib/file/find.rb ADDED
@@ -0,0 +1,504 @@
1
+ require 'date'
2
+ require 'rbconfig'
3
+
4
+ # For alternate implementations of Ruby, such as JRuby, that cannot
5
+ # build C extensions fall back to the Etc module.
6
+ begin
7
+ require 'sys/admin'
8
+ rescue LoadError
9
+ require 'etc'
10
+ end
11
+
12
+ class File::Find
13
+ # The version of the file-find library
14
+ VERSION = '0.3.4'
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 ? :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
403
+ }
404
+ rescue Errno::EACCES
405
+ next # Skip inaccessible directories
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
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}'"
434
+ end
435
+
436
+ @filetest << [sym, value]
437
+ else
438
+ unless VALID_OPTIONS.include?(key)
439
+ raise ArgumentError, "invalid option '#{key}'"
440
+ end
441
+
442
+ send("#{key}=", value)
443
+ end
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
477
+ end
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
+ end
@@ -68,7 +68,7 @@ class TC_File_Find < Test::Unit::TestCase
68
68
  end
69
69
 
70
70
  def test_version
71
- assert_equal('0.3.3', File::Find::VERSION)
71
+ assert_equal('0.3.4', File::Find::VERSION)
72
72
  end
73
73
 
74
74
  def test_path
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: file-find
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.3
4
+ version: 0.3.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Berger
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-08-03 00:00:00 -06:00
12
+ date: 2009-09-19 00:00:00 -06:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -30,7 +30,7 @@ dependencies:
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: 1.4.4
33
+ version: 1.5.2
34
34
  version:
35
35
  description: " The file-find library provides a better, more object oriented approach\n to finding files. It allows you to find files based on a variety of\n properties, such as access time, size, owner, etc. You can also limit\n directory depth.\n"
36
36
  email: djberg96@gmail.com
@@ -43,10 +43,13 @@ extra_rdoc_files:
43
43
  - CHANGES
44
44
  - MANIFEST
45
45
  files:
46
- - test/test_file_find.rb
47
- - README
48
46
  - CHANGES
47
+ - file-find.gemspec
48
+ - lib/file/find.rb
49
49
  - MANIFEST
50
+ - Rakefile
51
+ - README
52
+ - test/test_file_find.rb
50
53
  has_rdoc: true
51
54
  homepage: http://www.rubyforge.org/projects/shards
52
55
  licenses: