rbfind 1.12 → 2.1.1

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