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