rbfind 1.8 → 2.0.2

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