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.
- checksums.yaml +15 -0
- data/LICENSE +34 -0
- data/Rakefile +18 -0
- data/bin/rbfind +474 -0
- data/lib/humansiz.rb +129 -0
- data/lib/rbfind.rb +895 -0
- 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
|
+
|