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