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