rbfind 1.13 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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