rbfind 1.13 → 2.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,34 +2,13 @@
2
2
  # rbfind.rb -- Find replacement with many features
3
3
  #
4
4
 
5
- class Dir
5
+ require "rbfind/core"
6
+ require "rbfind/csv"
6
7
 
7
- SPECIAL_DIRS = %w(. ..)
8
- CUR_DIR, SUPER_DIR = *SPECIAL_DIRS
9
8
 
10
- method_defined? :each_child or def each_child
11
- s = SPECIAL_DIRS.dup
12
- each { |f|
13
- next if s.delete f
14
- yield f
15
- }
16
- end
17
-
18
- method_defined? :children or def children
19
- entries - SPECIAL_DIRS
20
- end
21
-
22
- end
23
-
24
- class File
25
- class Stat
26
- def identical? oth
27
- oth = self.class.new oth unless self.class === oth
28
- dev == oth.dev and ino == oth.ino
29
- end
30
- end
31
- end
9
+ module RbFind
32
10
 
11
+ VERSION = "2.2".freeze
33
12
 
34
13
  =begin rdoc
35
14
 
@@ -40,128 +19,128 @@ tool.
40
19
 
41
20
  In Ruby programs, you may call:
42
21
 
43
- RbFind.open do |f| puts f.path end
44
- RbFind.open "dir" do |f| puts f.path end
45
- RbFind.open "dir1", "dir2" do |f| puts f.path end
46
- RbFind.open %w(dir1 dir2) do |f| puts f.path end
47
- RbFind.open "dir", :max_depth => 3 do |f| puts f.path end
48
-
49
- # more terse
50
- RbFind.run do puts path end
51
- RbFind.run "dir" do puts path end
22
+ RbFind.run do puts path end
23
+ RbFind.run "dir" do puts path end
24
+ RbFind.run "dir1", "dir2" do puts path end
25
+ RbFind.run %w(dir1 dir2) do puts path end
26
+ RbFind.run "dir", :max_depth => 3 do puts path end
52
27
 
53
28
 
54
29
  == File properties
55
30
 
56
- f.name # file name (*)
57
- f.path # file path relative to working directory (*)
58
- f.fullpath # full file path (*)
59
- f.path! # directories with a slash appended (*)
60
- f.fullpath! # directories with a slash appended (*)
61
- f.dirname # dirname of path
62
- f.ext # file name extension
63
- f.without_ext # file name without extension
64
- f.depth # step depth
65
- f.hidden? # filename starting with "." (No Windows version, yet.)
66
- f.visible? # not hidden?
67
- f.stat # file status information (File::Stat object)
68
- f.mode # access mode (like 0755, 0644)
69
- f.age # age in seconds since walk started
70
- f.age_s # dto. (alias)
71
- f.age_secs # dto. (alias)
72
- f.age_m # age in minutes
73
- f.age_mins # dto. (alias)
74
- f.age_h # age in hours
75
- f.age_hours # dto. (alias)
76
- f.age_d # age in days
77
- f.age_days # dto. (alias)
78
- f.user # owner
79
- f.owner # dto. (alias)
80
- f.group # group owner
81
- f.readlink # symlink pointer or nil (*)
82
- f.broken_link? # what you expect
83
- f.arrow # ls-style "-> symlink" suffix (*)
31
+ name # file name (*)
32
+ path # file path relative to working directory (*)
33
+ fullpath # full file path (*)
34
+ path! # directories with a slash appended (*)
35
+ fullpath! # directories with a slash appended (*)
36
+ dirname # dirname of path
37
+ ext # file name extension
38
+ without_ext # file name without extension
39
+ depth # step depth
40
+ hidden? # filename starting with "." (No Windows version, yet.)
41
+ visible? # not hidden?
42
+ stat # file status information (File::Stat object)
43
+ mode # access mode (like 0755, 0644)
44
+ mtime # modify time (atime, ctime as well)
45
+ mage # age in seconds since walk started
46
+ age # alias for mage
47
+ user # owner
48
+ owner # dto. (alias)
49
+ group # group owner
50
+ user! # owner, "." if process owner
51
+ owner! # dto. (alias)
52
+ group! # group owner, "." if process owner
53
+ readlink # symlink pointer or nil (*)
54
+ broken_link? # what you expect
55
+ arrow # ls-style "-> symlink" suffix (*)
84
56
 
85
57
  # (*) = colored version available (see below)
86
58
 
87
- f.empty? # directory is empty
88
- f.entries # directory entries
59
+ empty? # directory is empty
60
+ entries # directory entries
89
61
 
90
- f.open { |o| ... } # open file
91
- f.read n = nil # read first n bytes, nil reads to eof
92
- f.lines { |l,i| ... } # open file and yield each |line,lineno|
93
- f.grep re # lines with `l =~ re and colsep path, i, l'
94
- f.binary? n = 1 # test whether first n blocks contain null characters
95
- f.bin? # alias for binary?
62
+ open { |o| ... } # open file
63
+ read n = nil # read first n bytes, nil reads to eof
64
+ lines { |l,i| ... } # open file and yield each |line,lineno|
65
+ grep re # lines with `l =~ re and colsep path, i, l'
66
+ binary? n = 1 # test whether first n blocks contain null characters
67
+ bin? # alias for binary?
96
68
 
97
- f.vimswap? # it is a Vim swapfile
69
+ vimswap? # it is a Vim swapfile
98
70
 
99
71
  Further will be redirected to the stat object (selective):
100
72
 
101
- f.directory?
102
- f.executable?
103
- f.file?
104
- f.pipe?
105
- f.socket?
106
- f.symlink?
73
+ directory?
74
+ executable?
75
+ file?
76
+ pipe?
77
+ socket?
78
+ symlink?
107
79
 
108
- f.readable?
109
- f.writable?
110
- f.size
111
- f.zero?
80
+ readable?
81
+ writable?
82
+ size
83
+ zero?
112
84
 
113
- f.uid
114
- f.gid
115
- f.owned?
116
- f.grpowned?
85
+ uid
86
+ gid
87
+ owned?
88
+ grpowned?
117
89
 
118
- f.dir? # alias for f.directory?
90
+ dir? # alias for directory?
119
91
 
120
92
  Derivated from stat:
121
93
 
122
- f.stype # one-letter (short) version of ftype
123
- f.modes # rwxr-xr-x style modes
94
+ stype # one-letter (short) version of ftype
95
+ modes # rwxr-xr-x style modes
124
96
 
125
- f.filesize # returns size for files, else nil
126
- f.filesize { |s| s > 1024 } # returns block result for files
97
+ filesize # returns size for files, else nil
98
+ filesize { |s| s > 1024 } # returns block result for files
127
99
 
128
100
 
129
101
  == Actions
130
102
 
131
- f.prune # do not descend directory; abort current entry
132
- f.novcs # omit .svn, CVS and .git directories
133
-
134
- f.colsep path, ... # output parameters in a line separated by colons
135
- f.col_sep # dto. (alias)
136
- f.tabsep path, ... # separate by tabs
137
- f.tab_sep #
138
- f.spcsep path, ... # separate by spaces
139
- f.spc_sep #
140
- f.spacesep path, ... #
141
- f.space_sep #
142
- f.csv sep, path, ... # separate by user-defined separator
143
-
144
- f.rename newname # rename, but leave it in the same directory
103
+ done # exit from current entry
104
+ done! # dto.
105
+ prune # do not descend directory; abort current entry
106
+ prune! # dto.
107
+ no_vcs # omit .svn, CVS and .git directories
108
+ novcs # dto.
109
+
110
+ colsep path, ... # output parameters in a line separated by colons
111
+ col_sep # dto. (alias)
112
+ tabsep path, ... # separate by tabs
113
+ tab_sep #
114
+ spcsep path, ... # separate by spaces
115
+ spc_sep #
116
+ spacesep path, ... #
117
+ space_sep #
118
+ p # alias for space_sep
119
+ csv sep, path, ... # separate by user-defined separator
120
+
121
+ rename newname # rename, but leave it in the same directory
122
+ mv newname # dto.
123
+ rm # remove
145
124
 
146
125
 
147
126
  == Color support
148
127
 
149
- f.cname # colorized name
150
- f.cpath # colorized path
151
- f.cpath! # colorized path!
152
- f.cfullpath # colorized fullpath
153
- f.cfullpath! # colorized fullpath!
154
- f.creadlink # colored symlink pointer
155
- f.carrow # colored "-> symlink" suffix
128
+ cname # colorized name
129
+ cpath # colorized path
130
+ cpath! # colorized path!
131
+ cfullpath # colorized fullpath
132
+ cfullpath! # colorized fullpath!
133
+ creadlink # colored symlink pointer
134
+ carrow # colored "-> symlink" suffix
156
135
 
157
- f.color arg # colorize argument
158
- f.colour arg # alias
136
+ color arg # colorize argument
137
+ colour arg # alias
159
138
 
160
139
  RbFind.colors str # define colors
161
140
  RbFind.colours str # alias
162
141
 
163
142
  Default color setup is "xxHbexfxcxdxbxegedabagacadAx".
164
- In case you did not call RbFind.colors, the environment variables
143
+ In case you did not call RbFind::Walk.colors, the environment variables
165
144
  RBFIND_COLORS and RBFIND_COLOURS are looked up. If neither is given
166
145
  but LSCOLORS is set, the fields 2-13 default to that.
167
146
  A Gnu LS_COLOR-style string may also be given, though glob patterns will
@@ -191,7 +170,7 @@ Derivated from stat:
191
170
  13 whiteout
192
171
  14 unknown
193
172
 
194
- f.suffix # ls-like suffixes |@=/%* for pipe, ..., executable
173
+ suffix # ls-like suffixes |@=/%* for pipe, ..., executable
195
174
 
196
175
 
197
176
  == Encoding issues
@@ -199,7 +178,7 @@ Derivated from stat:
199
178
  Ruby raises an ArgumentError if, for example, an ISO8859-1-encoded
200
179
  string gets read in as UTF-8-encoded and then is matched against a
201
180
  UTF-8-encoded regular expression. This will happen if you are
202
- running RbFind from an environment with something like
181
+ running RbFind::Walk from an environment with something like
203
182
  LANG="de_DE.UTF-8" and if you are searching directories containing
204
183
  single-byte encoded file names or files with single-byte or binary
205
184
  content.
@@ -221,752 +200,621 @@ variables because these will be used in the further processing.
221
200
 
222
201
  Find them all:
223
202
 
224
- RbFind.open do |f| puts f.path end
203
+ RbFind.run do puts path end
225
204
 
226
205
  Omit version control:
227
206
 
228
- RbFind.open "myproject" do |f|
229
- f.prune if f.name == ".svn"
230
- puts f.path
207
+ RbFind.run "myproject" do
208
+ prune if name == ".svn"
209
+ puts path
231
210
  end
232
211
 
233
212
  # or even
234
- RbFind.open "myproject" do |f|
235
- f.novcs
236
- puts f.path
213
+ RbFind.run "myproject" do
214
+ novcs
215
+ puts path
237
216
  end
238
217
 
239
218
  Mention directory contents before directory itself:
240
219
 
241
- RbFind.open "myproject", :depth => true do |f|
242
- puts f.path
220
+ RbFind.run "myproject", depth_first: true do
221
+ puts path
243
222
  end
244
223
 
245
224
  Limit search depth:
246
225
 
247
- RbFind.open :max_depth => 2 do |f|
248
- puts f.path
226
+ RbFind.run max_depth: 2 do
227
+ puts path
249
228
  end
250
229
 
251
230
  Unsorted (alphabetical sort is default):
252
231
 
253
- RbFind.open :sort => false do |f|
254
- puts f.path
232
+ RbFind.run sort: false do
233
+ puts path
255
234
  end
256
235
 
257
236
  Reverse sort:
258
237
 
259
- RbFind.open :sort => -1 do |f|
260
- puts f.path
238
+ RbFind.run sort: true, reverse: true do
239
+ puts path
261
240
  end
262
241
 
263
242
  Sort without case sensitivity and preceding dot:
264
243
 
265
244
  s = proc { |x| x =~ /^\.?/ ; $'.downcase }
266
- RbFind.open :sort => s do |f|
267
- puts f.path
245
+ RbFind.run sort: s do
246
+ puts path
268
247
  end
269
248
 
270
249
  =end
271
250
 
272
- class RbFind
273
251
 
274
- VERSION = "1.13".freeze
252
+ class Done < Exception ; end
253
+ class Prune < Exception ; end
275
254
 
276
255
  class <<self
277
- private :new
278
- def open *args, &block
279
- params = case args.last
280
- when Hash then args.pop
281
- end
282
- args.flatten!
283
- if args.any? then
284
- count = 0
285
- args.each do |path|
286
- f = new path, count, params, &block
287
- count = f.count
288
- end
289
- else
290
- f = new nil, 0, params, &block
291
- f.count
292
- end
293
- end
294
- def run *args, &block
295
- open *args do |f| f.instance_eval &block end
256
+ def run *args, **params, &block
257
+ Walk.run *args, **params, &block
296
258
  end
297
259
  end
298
260
 
299
- attr_reader :count, :wd, :start
300
-
301
- def initialize path, count, params = nil, &block
302
- @levels = []
303
- @block = block
304
-
305
- if params then
306
- params = params.dup
307
- dl = :do_level_depth if params.delete :depth
308
- md = params.delete :max_depth ; @max_depth = md.to_i if md
309
- st = params.delete :sort ; @sort = sort_parser st
310
- @follow = params.delete :follow
311
- @error = params.delete :error
312
- params.empty? or
313
- raise RuntimeError, "Unknown parameter(s): #{params.keys.join ','}."
314
- end
315
- @do_level = method dl||:do_level
316
-
317
- @ostat = $stdout.stat
318
- @ostat = nil unless @ostat.file?
319
-
320
- @start, @count = Time.now, count
321
- @wd = Dir.getwd
322
- if path then
323
- @max_depth += 1 if @max_depth
324
- File.lstat path
325
- @wd = nil unless absolute_path? path
326
- @levels.push path
327
- walk
328
- else
329
- build_path
330
- scan_dir
261
+ class Walk
262
+
263
+ class <<self
264
+ def run *args, **params, &block
265
+ i = new **params, &block
266
+ i.run *args
267
+ i.count
268
+ end
331
269
  end
332
- end
333
270
 
334
- private
271
+ private
335
272
 
336
- def append_slash s ; (File.directory? s) ? (File.join s, "") : s ; end
273
+ def initialize max_depth: nil, depth_first: nil, follow: nil,
274
+ sort: true, reverse: false, error: nil, &block
275
+ @max_depth = max_depth
276
+ @depth_first = depth_first
277
+ @follow = follow
278
+ @sort = sort_parser sort, reverse
279
+ @error = error
280
+ @block = block
337
281
 
338
- public
282
+ ostat = $stdout.stat
283
+ @ostat = ostat if ostat.file?
339
284
 
340
- def name ; @levels.last ; end
341
- def path ; @path ; end
342
- def fullpath ; @fullpath ; end
343
- def path! ; append_slash @path ; end
344
- def fullpath! ; append_slash @fullpath ; end
285
+ @wd, @start, @count = Dir.getwd, Time.now, 0
286
+ end
345
287
 
346
- def dirname
347
- d = File.dirname @fullpath
348
- File.basename d
349
- end
288
+ def sort_parser st, rev
289
+ r = case st
290
+ when Proc then proc { |l| l.sort_by! &st }
291
+ when nil, false, nil, "" then proc { }
292
+ else proc { |l| l.sort! }
293
+ end
294
+ rev ? proc { |l| r.call l ; l.reverse! } : r
295
+ end
350
296
 
351
- def ext ; File.extname name ; end
352
- def without_ext ; name[ /^(.+?)(?:\.[^.]+)?$/, 1 ].to_s ; end
297
+ public
353
298
 
354
- def depth ; @levels.size ; end
299
+ attr_reader :wd, :start, :count, :depth
355
300
 
356
- def hidden? ; name =~ /^\./ ; end
357
- def visible? ; not hidden? ; end
301
+ def run *args
302
+ @levels, @depth = [], 0
303
+ args.flatten!
304
+ args.compact!
305
+ if args.empty? then
306
+ visit_dir Dir::CUR_DIR
307
+ else
308
+ args.each { |base|
309
+ handle_error do
310
+ File.exists? base or raise "`#{base}` doesn't exist."
311
+ visit_depth base
312
+ end
313
+ }
314
+ end
315
+ ensure
316
+ @levels = @depth = nil
317
+ end
358
318
 
359
- def stat ; File.lstat @path ; end
360
- def mode ; stat.mode ; end
319
+ private
361
320
 
362
- def readlink
363
- File.readlink @path if stat.symlink?
364
- end
321
+ def join_path
322
+ (File.join @levels).freeze
323
+ end
365
324
 
366
- def creadlink
367
- if stat.symlink? then
368
- s = File.stat @path rescue nil
369
- l = File.readlink @path
370
- (col_stat l, s)
325
+ def visit filename
326
+ @depth += 1
327
+ visit_depth filename
328
+ ensure
329
+ @depth -= 1
371
330
  end
372
- end
373
331
 
374
- # :call-seq:
375
- # broken_link?() -> true or false
376
- #
377
- def broken_link?
378
- return unless stat.symlink?
379
- !File.stat @path rescue true
380
- end
332
+ def visit_depth filename
333
+ @levels.push filename.dup.freeze
334
+ p_, @path = @path, join_path
335
+ if @depth_first then
336
+ enter_dir
337
+ call_block or raise "#{self.class}: prune doesn't work with :depth_first."
338
+ else
339
+ call_block and enter_dir
340
+ end
341
+ @count += 1
342
+ ensure
343
+ @path = p_
344
+ @levels.pop
345
+ end
346
+
347
+ def enter_dir
348
+ return unless File.directory? @path
349
+ if File.symlink? @path then
350
+ return unless @follow and handle_error do
351
+ d = @path.dup
352
+ while d != Dir::CUR_DIR do
353
+ d, = File.split d
354
+ raise "cyclic recursion in #@path" if File.identical? d, @path
355
+ end
356
+ true
357
+ end
358
+ end
359
+ handle_error do
360
+ visit_dir @path
361
+ end
362
+ end
381
363
 
382
- ARROW = " -> " # :nodoc:
364
+ def visit_dir dir
365
+ return if @max_depth and @max_depth == @depth
366
+ list = (Dir.new dir).children
367
+ @sort.call list
368
+ list.each { |f| visit f }
369
+ ensure
370
+ end
383
371
 
384
- def arrow
385
- ARROW + (File.readlink @path) if stat.symlink?
386
- end
372
+ def call_block
373
+ e = Entry.new @levels.last, @path, self
374
+ handle_error do
375
+ $_, $. = e.name, count
376
+ begin
377
+ e.instance_eval &@block
378
+ rescue Done
379
+ end
380
+ if !(e.name.equal? @levels.last) && e.name != @levels.last then
381
+ if e.name then
382
+ e.name == (File.basename e.name) or
383
+ raise "#{self.class}: rename to `#{e.name}' may not be a path."
384
+ e.name.freeze
385
+ @levels.pop
386
+ @levels.push e.name
387
+ p, @path = @path, join_path
388
+ File.rename p, @path
389
+ else
390
+ if e.dir? then
391
+ Dir.rmdir @path
392
+ else
393
+ File.unlink @path
394
+ end
395
+ end
396
+ end
397
+ true
398
+ end
399
+ rescue Prune
400
+ end
387
401
 
388
- def carrow
389
- r = creadlink
390
- ARROW + r if r
391
- end
402
+ def handle_error
403
+ yield
404
+ rescue
405
+ case @error
406
+ when Proc then @error.call
407
+ when String then instance_eval @error
408
+ else raise
409
+ end
410
+ nil
411
+ end
392
412
 
393
- def now ; @start ; end
394
- def age ; @start - stat.mtime ; end
395
- alias age_secs age
396
- alias age_s age_secs
397
-
398
- # :stopdoc:
399
- MINUTE = 60
400
- HOUR = 60*MINUTE
401
- DAY = 24*HOUR
402
- # :startdoc:
403
-
404
- def age_mins ; age / MINUTE ; end
405
- alias age_m age_mins
406
- def age_hours ; age / HOUR ; end
407
- alias age_h age_hours
408
- def age_days ; age / DAY ; end
409
- alias age_d age_days
410
-
411
- private
412
-
413
- def method_missing sym, *args, &block
414
- stat.send sym, *args, &block
415
- rescue NoMethodError
416
- super
417
413
  end
418
414
 
419
- public
420
-
421
- def dir? ; stat.directory? ; end
422
-
423
- # :call-seq:
424
- # stype() -> str
425
- #
426
- def stype
427
- m = stat.mode >> 12 rescue nil
428
- case m
429
- when 001 then "p"
430
- when 002 then "c"
431
- when 004 then "d"
432
- when 006 then "b"
433
- when 010 then "-"
434
- when 012 then "l"
435
- when 014 then "s"
436
- when 016 then "w"
437
- when nil then "#"
438
- else "?"
415
+
416
+ class Entry
417
+
418
+ attr_reader :path, :name
419
+
420
+ def initialize name, path, walk
421
+ @name, @path, @walk = name, path, walk
439
422
  end
440
- end
441
423
 
442
- # :call-seq:
443
- # modes() -> str
444
- #
445
- def modes
446
- m = stat.mode
447
- r = ""
448
- 3.times {
449
- h = m & 07
450
- m >>= 3
451
- r.insert 0, ((h & 01).nonzero? ? "x" : "-")
452
- r.insert 0, ((h & 02).nonzero? ? "w" : "-")
453
- r.insert 0, ((h & 04).nonzero? ? "r" : "-")
454
- }
455
- if (m & 04).nonzero? then
456
- r[ 2] = r[ 2, 1] == "x" ? "s" : "S"
457
- end
458
- if (m & 02).nonzero? then
459
- r[ 5] = r[ 5, 1] == "x" ? "s" : "S"
460
- end
461
- if (m & 01).nonzero? then
462
- r[ 8] = r[ 8, 1] == "x" ? "t" : "T"
463
- end
464
- r
465
- end
424
+ def depth ; @walk.depth ; end
425
+ def now ; @walk.start ; end
426
+
427
+ def fullpath ; @fullpath ||= File.absolute_path @path, @walk.wd ; end
428
+
429
+ def stat ; @stat ||= File.lstat @path ; end
430
+ def rstat ; @rstat ||= File.stat @path ; end
431
+
432
+
433
+ private
434
+ def append_slash s ; (File.directory? s) ? (File.join s, "") : s ; end
435
+ public
466
436
 
467
- # :call-seq:
468
- # filesize => nil or int
469
- # filesize { |size| ... } => obj
470
- #
471
- # Returns the files size. When the object is not a regular file,
472
- # nil will be returned or the block will not be called.
473
- #
474
- def filesize
475
- if block_given? then
476
- yield stat.size if file?
477
- else
478
- stat.size if file?
437
+ def path! ; append_slash path ; end
438
+ def fullpath! ; append_slash fullpath ; end
439
+
440
+ def dirname
441
+ File.basename File.dirname fullpath
479
442
  end
480
- end
481
443
 
482
444
 
483
- def cname ; color name ; end
484
- def cpath ; color path ; end
485
- def cfullpath ; color fullpath ; end
486
- def cpath! ; color path! ; end
487
- def cfullpath! ; color fullpath! ; end
445
+ def ext ; File.extname name ; end
446
+ def without_ext ; name[ /^(.+?)(?:\.[^.]+)?$/, 1 ].to_s ; end
488
447
 
489
- def color arg
490
- col_stat arg, stat
491
- end
492
- alias colour color
448
+ def hidden? ; name =~ /^\./ ; end
449
+ def visible? ; not hidden? ; end
493
450
 
494
- DEFAULT_COLORS = "xxHbexfxcxdxbxegedabagacadAx"
495
451
 
496
- class <<self
452
+ def mode ; stat.mode ; end
497
453
 
498
- def colors str
499
- if str =~ /:/ then
500
- h = {}
501
- (str.split ":").each { |a|
502
- t, c = a.split "="
503
- h[ t] = c
504
- }
505
- %w(rs or di ln so pi ex bd cd su sg tw ow - -).map { |t| h[ t] }
454
+ private
455
+ def method_missing sym, *args, &block
456
+ if stat.respond_to? sym then
457
+ stat.send sym, *args, &block
506
458
  else
507
- cols = []
508
- str.scan /(.)(.)/i do
509
- fg, bg = $~.captures.map { |x| x.downcase.ord - ?a.ord }
510
- a = []
511
- case fg
512
- when 0..7 then a.push 30 + fg
513
- end
514
- a.push 1 if $1 == $1.upcase
515
- case bg
516
- when 0..7 then a.push 40 + bg
517
- end
518
- e = a.join ";"
519
- cols.push e
520
- end
521
- cols
459
+ super
522
460
  end
523
461
  end
524
- alias colours colors
462
+ public
525
463
 
526
- def colored arg, num
527
- @cols ||= colors col_str
528
- "\e[#{@cols[num]}m#{arg}\e[m"
464
+ def dir? ; stat.directory? ; end
465
+
466
+ def aage ; @walk.start - stat.atime ; end
467
+ def mage ; @walk.start - stat.mtime ; end
468
+ def cage ; @walk.start - stat.ctime ; end
469
+ alias age mage
470
+
471
+ # :call-seq:
472
+ # filesize => nil or int
473
+ # filesize { |size| ... } => obj
474
+ #
475
+ # Returns the files size. When the object is not a regular file,
476
+ # nil will be returned or the block will not be called.
477
+ #
478
+ def filesize
479
+ stat.file? or return
480
+ if block_given? then
481
+ yield stat.size
482
+ else
483
+ stat.size
484
+ end
529
485
  end
530
- alias coloured colored
531
486
 
532
487
  private
488
+ def etc
489
+ Etc
490
+ rescue NameError
491
+ require "etc" and retry
492
+ raise
493
+ end
494
+ def get_user u ; (etc.getpwuid u).name rescue u.to_s ; end
495
+ def get_group g ; (etc.getgrgid g).name rescue g.to_s ; end
496
+ public
533
497
 
534
- def col_str
535
- ENV[ "RBFIND_COLORS"] || ENV[ "RBFIND_COLOURS"] || ENV[ "LS_COLORS"] || (
536
- env = DEFAULT_COLORS.dup
537
- els = ENV[ "LSCOLORS"]
538
- if els then
539
- env[ 2*2, els.length] = els
540
- end
541
- env
542
- )
498
+ def user
499
+ get_user stat.uid
543
500
  end
501
+ alias owner user
544
502
 
545
- end
503
+ def user!
504
+ u = stat.uid
505
+ u == Process.uid ? "." : (get_user u)
506
+ end
507
+ alias owner! user!
546
508
 
509
+ def group
510
+ get_group stat.gid
511
+ end
547
512
 
548
- # :call-seq:
549
- # suffix() -> str
550
- #
551
- def suffix
552
- m = stat.mode >> 12 rescue nil
553
- case m
554
- when 001 then "|"
555
- when 002 then " "
556
- when 004 then "/"
557
- when 006 then " "
558
- when 010 then stat.executable? ? "*" : " "
559
- when 012 then "@"
560
- when 014 then "="
561
- when 016 then "%"
562
- else "?"
513
+ def group!
514
+ g = stat.gid
515
+ g == Process.gid ? "." : (get_group g)
563
516
  end
564
- end
565
517
 
566
- autoload :Etc, "etc"
567
518
 
568
- # :call-seq:
569
- # user() -> str
570
- #
571
- # Return user name or uid as string if unavailable.
572
- #
573
- def user
574
- u = stat.uid
575
- (Etc.getpwuid u).name rescue u.to_s
576
- end
577
- alias owner user
578
-
579
- # :call-seq:
580
- # group() -> str
581
- #
582
- # Return group name or gid as string if unavailable.
583
- #
584
- def group
585
- g = stat.gid
586
- (Etc.getgrgid g).name rescue g.to_s
587
- end
519
+ def readlink ; File.readlink @path if stat.symlink? ; end
588
520
 
521
+ def broken_link?
522
+ return unless stat.symlink?
523
+ rstat
524
+ false
525
+ rescue
526
+ true
527
+ end
528
+ alias broken? broken_link?
589
529
 
590
- # :call-seq:
591
- # empty?() -> true or false
592
- #
593
- # Look up if the directory is empty. If the object is not a directory,
594
- # +nil+ is returned.
595
- #
596
- def empty?
597
- read_dir.each_child { |f| return false }
598
- true
599
- rescue Errno::ENOTDIR
600
- end
530
+ ARROW = " -> "
531
+ def arrow
532
+ ARROW + (File.readlink @path) if stat.symlink?
533
+ end
601
534
 
602
- # :call-seq:
603
- # contains?( name) -> true or false
604
- #
605
- # Check whether a directory contains an entry.
606
- #
607
- def contains? name
608
- c = File.join @path, name
609
- File.exists? c
610
- end
611
535
 
612
- # :call-seq:
613
- # entires() -> ary
614
- #
615
- # Return all entries in an array. If the object is not a directory,
616
- # +nil+ is returned.
617
- #
618
- def entries
619
- read_dir.children
620
- rescue Errno::ENOTDIR
621
- end
622
- alias children entries
623
-
624
- # :call-seq:
625
- # open() { |h| ... } -> obj
626
- #
627
- # Open the file for reading. If the object is not a regular file,
628
- # nothing will be done.
629
- #
630
- def open &block
631
- if @ostat and @ostat.identical? @path then
632
- raise "Refusing to open output file."
633
- end
634
- handle_error Errno::EACCES do
536
+
537
+ # :call-seq:
538
+ # empty?() -> true or false
539
+ #
540
+ # Look up if the directory is empty. If the object is not a directory
541
+ # or not accessible, +nil+ is returned.
542
+ #
543
+ def empty?
544
+ (Dir.new @path).each_child { |f| return false }
545
+ true
546
+ rescue Errno::ENOTDIR
547
+ end
548
+
549
+ # :call-seq:
550
+ # contains?( name) -> true or false
551
+ #
552
+ # Check whether a directory contains an entry.
553
+ #
554
+ def contains? name
555
+ File.exists? File.join @path, name
556
+ end
557
+
558
+ # :call-seq:
559
+ # entries() -> ary
560
+ #
561
+ # Return all entries in an array. If the object is not a directory,
562
+ # +nil+ is returned.
563
+ #
564
+ def entries
565
+ (Dir.new @path).children
566
+ rescue Errno::ENOTDIR
567
+ end
568
+ alias children entries
569
+
570
+
571
+ def vcs?
572
+ %w(CVS .svn .git .hg .fslckout).include? name
573
+ end
574
+
575
+
576
+ # :call-seq:
577
+ # open() { |h| ... } -> obj
578
+ #
579
+ # Open the file for reading. If the object is not a regular file,
580
+ # nothing will be done.
581
+ #
582
+ def open &block
583
+ @ostat and @ostat.identical? @path and
584
+ raise "Refusing to open output file."
635
585
  File.open @path, &block if file?
636
586
  end
637
- end
638
587
 
639
- # :call-seq:
640
- # read( n = nil) -> str or nil
641
- # read( n = nil) { |b| ... } -> nil
642
- #
643
- # Read the first +n+ bytes or return +nil+ for others than regular
644
- # files. +nil+ reads to end of file. If a block is given, chonks of
645
- # +n+ bytes (or all) will be yielded.
646
- #
647
- def read n = nil
648
- open { |o|
649
- if block_given? then
650
- if n then
651
- while (r = o.read n) do
652
- yield r
588
+ # :call-seq:
589
+ # read( n = nil) -> str or nil
590
+ # read( n = nil) { |b| ... } -> nil
591
+ #
592
+ # Read the first +n+ bytes or return +nil+ for others than regular
593
+ # files. +nil+ reads to end of file. If a block is given, chonks of
594
+ # +n+ bytes (or all) will be yielded.
595
+ #
596
+ def read n = nil
597
+ open { |o|
598
+ if block_given? then
599
+ if n then
600
+ while (r = o.read n) do
601
+ yield r
602
+ end
603
+ else
604
+ yield o.read
653
605
  end
654
606
  else
655
- yield o.read
607
+ o.read n
656
608
  end
657
- else
658
- o.read n
659
- end
660
- }
661
- end
609
+ }
610
+ end
662
611
 
663
- # :call-seq:
664
- # lines { |l,i| ... } -> nil
665
- #
666
- # Yield line by line together with the line number <code>i</code>.
667
- #
668
- def lines
669
- block_given? or return lines do end
670
- r = false
671
- open { |file|
672
- n = 0
673
- file.each_line { |l|
674
- l.chomp!
675
- n += 1
676
- set_predefs l, n
677
- r ||= true if yield l, n
612
+ # :call-seq:
613
+ # lines { |l,i| ... } -> nil
614
+ #
615
+ # Yield line by line together with the line number <code>i</code>.
616
+ #
617
+ def lines
618
+ block_given? or return lines do end
619
+ r = false
620
+ open { |file|
621
+ n = 0
622
+ file.each_line { |l|
623
+ l.chomp!
624
+ n += 1
625
+ $_, $. = l, n
626
+ r ||= true if yield l, n
627
+ }
628
+ r
678
629
  }
679
- r
680
- }
681
- end
630
+ end
682
631
 
683
- def grep re, color = nil
684
- case color
685
- when /\A\d+(?:;\d+)*\z/, nil, false then
686
- when true then color = "31;1" # red
687
- else raise "Illegal color spec: #{color}"
688
- end
689
- lines { |l,i|
690
- l.scrub!
691
- l =~ re or next
692
- if color then
693
- l = "#$`\e[#{color}m#$&\e[m#$'"
632
+ def grep re, color = nil
633
+ case color
634
+ when /\A\d+(?:;\d+)*\z/, nil, false then
635
+ when true then color = "31;1" # red
636
+ else raise "Illegal color spec: #{color}"
694
637
  end
695
- colsep @path, i, l
696
- true
697
- }
698
- end
638
+ lines { |l,i|
639
+ l.scrub!
640
+ l =~ re or next
641
+ color and l = "#$`\e[#{color}m#$&\e[m#$'"
642
+ colsep @path, i, l
643
+ true
644
+ }
645
+ end
699
646
 
700
- BLOCK_SIZE = 512 # :nodoc:
701
-
702
- # :call-seq:
703
- # binary?( n = 1) -> true or false
704
- #
705
- # Test whether the first <code>n</code> blocks contain null characters.
706
- #
707
- def binary? n = 1
708
- open { |file|
709
- loop do
710
- if n then
711
- break if n <= 0
712
- n -= 1
647
+ # :call-seq:
648
+ # binary?( n = 1) -> true or false
649
+ #
650
+ # Test whether the first <code>n</code> blocks contain null characters.
651
+ #
652
+ def binary? n = 1
653
+ bs = stat.blksize
654
+ open { |file|
655
+ loop do
656
+ if n then
657
+ break if n <= 0
658
+ n -= 1
659
+ end
660
+ b = file.read bs
661
+ b or break
662
+ return true if b[ "\0"]
713
663
  end
714
- b = file.read BLOCK_SIZE
715
- b or break
716
- return true if b[ "\0"]
664
+ }
665
+ false
666
+ end
667
+ alias bin? binary?
668
+
669
+ def vimswap?
670
+ if name =~ /\A(\..+)?\.sw[a-z]\z/i then
671
+ mark = read 5
672
+ mark == "b0VIM"
717
673
  end
718
- }
719
- false
720
- end
721
- alias bin? binary?
674
+ end
722
675
 
723
676
 
724
- # :stopdoc:
725
- class Prune < Exception ; end
726
- # :startdoc:
727
-
728
- # :call-seq:
729
- # prune() -> (does not return)
730
- #
731
- # Abandon the current object (directory) and ignore all subdirectories.
732
- #
733
- def prune ; raise Prune ; end
734
-
735
- # :call-seq:
736
- # novcs() -> nil
737
- #
738
- # Perform <code>prune</code> if the current object is a CVS, Subversion or
739
- # Git directory.
740
- #
741
- def novcs
742
- prune if %w(CVS .svn .git .hg .fslckout).include? name
743
- end
744
- alias no_vcs novcs
745
-
746
- # :call-seq:
747
- # vimswap? -> true or false
748
- #
749
- # Check whether the current object is a Vim swapfile.
750
- #
751
- def vimswap?
752
- if name =~ /\A(\..+)?\.sw[a-z]\z/i then
753
- mark = read 5
754
- mark == "b0VIM"
755
- end
756
- end
757
677
 
758
- # Windows has ":" in filenames ("C:\...")
759
- COLON = File::ALT_SEPARATOR ? "|" : ":" # :nodoc:
678
+ def done ; raise Done ; end
679
+ alias done! done
760
680
 
761
- def colsep *args
762
- csv COLON, *args
763
- end
764
- alias col_sep colsep
681
+ def prune ; raise Prune ; end
682
+ alias prune! prune
765
683
 
766
- def tabsep *args
767
- csv "\t", *args
768
- end
769
- alias tab_sep tabsep
684
+ def novcs
685
+ prune if vcs?
686
+ end
687
+ alias no_vcs novcs
770
688
 
771
- def spcsep *args
772
- csv " ", *args
773
- end
774
- alias spc_sep spcsep
775
- alias space_sep spc_sep
776
- alias spacesep spcsep
777
- alias p spcsep
778
-
779
- def csv sep, *args
780
- e = args.join sep
781
- puts e
782
- end
783
689
 
784
- def rename newname
785
- p = @path
786
- nb = File.basename newname
787
- newname == nb or raise RuntimeError,
788
- "#{self.class}: rename to `#{newname}' may not be a path."
789
- nb.freeze
790
- @levels.pop
791
- @levels.push nb
792
- build_path
793
- File.rename p, @path
794
- nil
795
- end
690
+ include Csv
796
691
 
797
- private
798
692
 
799
- def sort_parser st
800
- case st
801
- when Proc then st
802
- when Numeric then st
803
- when true then +1
804
- when false then 0
805
- when nil then +1
806
- else
807
- case st.to_s
808
- when "^", "reverse", /^desc/, /^-/ then -1
809
- when "unsorted", "*" then 0
810
- else +1
811
- end
812
- end
813
- end
693
+ def rename newname ; @name = newname ; end
694
+ alias mv rename
814
695
 
815
- def absolute_path? p
816
- loop do
817
- q = File.dirname p
818
- break if q == p
819
- p = q
820
- end
821
- p != Dir::CUR_DIR
822
- end
696
+ def rm ; @name = nil ; end
823
697
 
824
- def build_path
825
- @path = File.join @levels
826
- @fullpath = if @wd then
827
- File.join @wd, @path
828
- else
829
- @path
830
- end
831
- if @path.empty? then @path = Dir::CUR_DIR end
832
- @fullpath.freeze
833
- @path.freeze
834
- end
835
698
 
836
- def walk
837
- return if @max_depth and depth > @max_depth
838
- build_path
839
- @count += 1
840
- @do_level.call
841
- end
699
+ def cname ; color name ; end
700
+ def cpath ; color path ; end
701
+ def cfullpath ; color fullpath ; end
702
+ def cpath! ; color path! ; end
703
+ def cfullpath! ; color fullpath! ; end
842
704
 
843
- def do_level
844
- begin
845
- call_block
846
- rescue Prune
847
- return
705
+ def creadlink
706
+ l = readlink
707
+ if l then
708
+ s = rstat rescue nil
709
+ color_stat l, s
710
+ end
848
711
  end
849
- scan_dir
850
- end
851
712
 
852
- def do_level_depth
853
- begin
854
- path, fullpath = @path, @fullpath
855
- scan_dir
856
- ensure
857
- @path, @fullpath = path, fullpath
713
+ def carrow
714
+ r = creadlink
715
+ ARROW + r if r
858
716
  end
859
- begin
860
- call_block
861
- rescue Prune
862
- raise RuntimeError, "#{self.class}: prune doesn't work with :depth."
863
- end
864
- end
865
717
 
866
- def call_block
867
- set_predefs name, count
868
- @block.call self
869
- end
870
-
871
- def set_predefs l, n
872
- b = @block.binding
873
- b.local_variable_set "_", [ l, n]
874
- b.eval "$_, $. = *_"
875
- end
876
718
 
877
- def read_dir
878
- handle_error Errno::EACCES do
879
- Dir.new @path
719
+ def color arg
720
+ color_stat arg, stat
880
721
  end
881
- end
722
+ alias colour color
882
723
 
883
- def scan_dir
884
- return unless File.directory? @path
885
- if File.symlink? @path then
886
- return unless @follow and handle_error do
887
- d = @path
888
- while d != Dir::CUR_DIR do
889
- d, = File.split d
890
- raise "circular recursion in #@path" if File.identical? d, @path
891
- end
892
- true
724
+ private
725
+
726
+ def color_stat arg, s
727
+ m = s.mode if s
728
+ code = case m && m >> 12
729
+ when 001 then 5
730
+ when 002 then 8
731
+ when 004 then
732
+ if (m & 0002).nonzero? then
733
+ if (m & 01000).nonzero? then 11
734
+ else 12
735
+ end
736
+ else 2
737
+ end
738
+ when 006 then 7
739
+ when 010 then
740
+ if (m & 0111).nonzero? then
741
+ if (m & 04000).nonzero? then 9
742
+ elsif (m & 02000).nonzero? then 10
743
+ else 6
744
+ end
745
+ else col_type or 0
746
+ end
747
+ when 012 then 3
748
+ when 014 then 4
749
+ when 016 then 13
750
+ when nil then 1
751
+ else 14
893
752
  end
753
+ self.class.colored arg, code
894
754
  end
895
- dir = (read_dir or return).children
896
- if @sort.respond_to? :call then
897
- dir = dir.sort_by &@sort
898
- elsif @sort and @sort.nonzero? then
899
- dir.sort!
900
- dir.reverse! if @sort < 0
901
- end
902
- dir.each { |f|
903
- f.freeze
904
- begin
905
- @levels.push f
906
- walk
907
- ensure
908
- @levels.pop
909
- end
910
- }
911
- end
912
755
 
913
- def handle_error err = nil
914
- yield
915
- rescue err||StandardError
916
- if @error.respond_to? :call then
917
- @error.call
918
- elsif @error then
919
- instance_eval @error
920
- else
921
- raise
756
+ def col_type
757
+ # Overwrite this to define custom colors
758
+ # Example:
759
+ # case ext
760
+ # when ".png", /\.jpe?g$/, /\.tiff?$/ then 15
761
+ # when /\.tar\.(gz|bz2)$/ then 16
762
+ # end
922
763
  end
923
- nil
924
- end
925
764
 
926
- def col_stat arg, s
927
- m = s.mode if s
928
- code = case m && m >> 12
929
- when 001 then 5
930
- when 002 then 8
931
- when 004 then
932
- if (m & 0002).nonzero? then
933
- if (m & 01000).nonzero? then 11
934
- else 12
935
- end
936
- else 2
937
- end
938
- when 006 then 7
939
- when 010 then
940
- col_type or \
941
- if (m & 0111).nonzero? then
942
- if (m & 04000).nonzero? then 9
943
- elsif (m & 02000).nonzero? then 10
944
- else 6
765
+ DEFAULT_COLORS = "xxHbexfxcxdxbxegedabagacadAx"
766
+
767
+ class <<self
768
+
769
+ def colored arg, num
770
+ colors col_str
771
+ "\e[#{@colors[num]}m#{arg}\e[m"
772
+ end
773
+ alias coloured colored
774
+
775
+ def colors str
776
+ @colors ||= if str =~ /:/ then
777
+ h = {}
778
+ (str.split ":").each { |a|
779
+ t, c = a.split "="
780
+ h[ t] = c
781
+ }
782
+ %w(rs or di ln so pi ex bd cd su sg tw ow - -).map { |t| h[ t] }
783
+ else
784
+ cols = []
785
+ str.scan /(.)(.)/i do
786
+ fg, bg = $~.captures.map { |x| x.downcase.ord - ?a.ord }
787
+ a = []
788
+ case fg
789
+ when 0..7 then a.push 30 + fg
790
+ end
791
+ a.push 1 if $1 == $1.upcase
792
+ case bg
793
+ when 0..7 then a.push 40 + bg
794
+ end
795
+ e = a.join ";"
796
+ cols.push e
945
797
  end
946
- else 0
798
+ cols
947
799
  end
948
- when 012 then 3
949
- when 014 then 4
950
- when 016 then 13
951
- when nil then 1
952
- else 14
953
- end
954
- self.class.colored arg, code
955
- end
800
+ end
801
+ alias colours colors
956
802
 
957
- def col_type
958
- # Overwrite this to define custom colors
959
- # Example:
960
- # case ext
961
- # when ".png", /\.jpe?g$/, /\.tiff?$/ then 15
962
- # when /\.tar\.(gz|bz2)$/ then 16
963
- # end
964
- end
803
+ private
804
+
805
+ def col_str
806
+ ENV[ "RBFIND_COLORS"] || ENV[ "RBFIND_COLOURS"] || ENV[ "LS_COLORS"] || (
807
+ env = DEFAULT_COLORS.dup
808
+ els = ENV[ "LSCOLORS"]
809
+ if els then
810
+ env[ 2*2, els.length] = els
811
+ end
812
+ env
813
+ )
814
+ end
965
815
 
966
- class <<self
967
- def find *args
968
- raise NotImplementedError, "This is not the standard Find."
969
816
  end
817
+
970
818
  end
971
819
 
972
820
  end