rbfind 1.3.1

Sign up to get free protection for your applications and to get access to all the features.
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
+