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