fun_with_files 0.0.14 → 0.0.18
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/CHANGELOG.markdown +15 -3
- data/Gemfile +17 -7
- data/{README.rdoc → README.markdown} +12 -11
- data/Rakefile +3 -3
- data/VERSION +1 -1
- data/lib/fun_with/files/bootstrapper.rb +87 -0
- data/lib/fun_with/files/core_extensions/file.rb +15 -3
- data/lib/fun_with/files/core_extensions/set.rb +12 -0
- data/lib/fun_with/files/core_extensions/true_class.rb +11 -0
- data/lib/fun_with/files/digest_methods.rb +50 -17
- data/lib/fun_with/files/directory_builder.rb +9 -4
- data/lib/fun_with/files/downloader.rb +29 -16
- data/lib/fun_with/files/errors.rb +9 -1
- data/lib/fun_with/files/file_manipulation_methods.rb +25 -15
- data/lib/fun_with/files/file_orderer.rb +2 -0
- data/lib/fun_with/files/file_path.rb +242 -156
- data/lib/fun_with/files/file_path_class_methods.rb +23 -2
- data/lib/fun_with/files/file_permission_methods.rb +18 -7
- data/lib/fun_with/files/file_requirements.rb +63 -7
- data/lib/fun_with/files/requirements/manager.rb +104 -0
- data/lib/fun_with/files/root_path.rb +3 -3
- data/lib/fun_with/files/stat_methods.rb +33 -0
- data/lib/fun_with/files/string_behavior.rb +6 -2
- data/lib/fun_with/files/utils/byte_size.rb +143 -0
- data/lib/fun_with/files/utils/opts.rb +26 -0
- data/lib/fun_with/files/utils/succession.rb +47 -0
- data/lib/fun_with/files/utils/timestamp.rb +47 -0
- data/lib/fun_with/files/utils/timestamp_format.rb +31 -0
- data/lib/fun_with/files/watcher.rb +157 -0
- data/lib/fun_with/files/watchers/directory_watcher.rb +67 -0
- data/lib/fun_with/files/watchers/file_watcher.rb +45 -0
- data/lib/fun_with/files/watchers/missing_watcher.rb +23 -0
- data/lib/fun_with/files/watchers/node_watcher.rb +44 -0
- data/lib/fun_with/testing/assertions/fun_with_files.rb +91 -0
- data/lib/fun_with/testing/test_case_extensions.rb +12 -0
- data/lib/fun_with_files.rb +5 -31
- data/test/helper.rb +13 -5
- data/test/test_core_extensions.rb +6 -0
- data/test/test_descent.rb +2 -2
- data/test/test_directory_builder.rb +29 -10
- data/test/test_extension_methods.rb +62 -0
- data/test/test_file_manipulation.rb +4 -4
- data/test/test_file_path.rb +83 -56
- data/test/test_file_requirements.rb +36 -0
- data/test/test_fun_with_files.rb +1 -1
- data/test/test_fwf_assertions.rb +62 -0
- data/test/test_moving_files.rb +111 -0
- data/test/test_permission_methods.rb +22 -0
- data/test/test_root_path.rb +9 -0
- data/test/test_stat_methods.rb +17 -0
- data/test/test_timestamping.rb +74 -0
- data/test/test_utils_bytesize.rb +71 -0
- data/test/test_utils_succession.rb +30 -0
- data/test/test_watchers.rb +196 -0
- metadata +59 -13
- /data/lib/fun_with/files/core_extensions/{false.rb → false_class.rb} +0 -0
- /data/lib/fun_with/files/core_extensions/{nil.rb → nil_class.rb} +0 -0
@@ -1,12 +1,11 @@
|
|
1
1
|
module FunWith
|
2
2
|
module Files
|
3
3
|
class FilePath < Pathname
|
4
|
-
|
5
4
|
SUCC_DIGIT_COUNT = 6
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
5
|
+
|
6
|
+
def initialize( *args, &block )
|
7
|
+
super( File.join( *(args.map(&:to_s) ) ) )
|
8
|
+
_yield_and_return( self, &block )
|
10
9
|
end
|
11
10
|
|
12
11
|
attr_accessor :path
|
@@ -25,33 +24,45 @@ module FunWith
|
|
25
24
|
Dir.mktmpdir.fwf_filepath
|
26
25
|
end
|
27
26
|
end
|
27
|
+
|
28
|
+
# The file is created within a temporary directory
|
29
|
+
def self.tmpfile( ext = :tmp, &block )
|
30
|
+
filename = rand( 2 ** 64 ).to_s(16).fwf_filepath.ext( ext )
|
31
|
+
|
32
|
+
if block_given?
|
33
|
+
self.tmpdir do |tmp|
|
34
|
+
yield tmp.join( filename ).touch
|
35
|
+
end
|
36
|
+
else
|
37
|
+
self.tmpdir.join( filename ).touch
|
38
|
+
end
|
39
|
+
end
|
28
40
|
|
29
41
|
def join( *args, &block )
|
30
|
-
joined_path = self.class.new( super(*args) )
|
31
|
-
|
32
|
-
joined_path
|
42
|
+
joined_path = self.class.new( super( *(args.map(&:to_s) ) ) )
|
43
|
+
_yield_and_return( joined_path, &block )
|
33
44
|
end
|
34
45
|
|
35
46
|
def join!( *args, &block )
|
36
47
|
@path = self.join( *args, &block ).to_str
|
37
|
-
self
|
48
|
+
_yield_and_return( self, &block )
|
38
49
|
end
|
39
50
|
|
40
|
-
def / arg
|
41
|
-
self.join( arg )
|
51
|
+
def /( arg, &block )
|
52
|
+
_yield_and_return( self.join( arg ), &block )
|
42
53
|
end
|
43
54
|
|
44
|
-
def [] *args
|
45
|
-
self.join(*args)
|
55
|
+
def []( *args, &block )
|
56
|
+
_yield_and_return( self.join(*args), &block )
|
46
57
|
end
|
47
58
|
|
48
59
|
|
49
|
-
def doesnt_exist?
|
50
|
-
self.exist? == false
|
60
|
+
def doesnt_exist?( &block )
|
61
|
+
_yield_self_on_success( self.exist? == false, &block )
|
51
62
|
end
|
52
63
|
|
53
|
-
def not_a_file?
|
54
|
-
! self.file
|
64
|
+
def not_a_file?( &block )
|
65
|
+
_yield_self_on_success( ! self.file?, &block )
|
55
66
|
end
|
56
67
|
|
57
68
|
alias :absent? :doesnt_exist?
|
@@ -181,8 +192,16 @@ module FunWith
|
|
181
192
|
end
|
182
193
|
end
|
183
194
|
|
184
|
-
def entries
|
185
|
-
self.glob( :recurse => false )
|
195
|
+
def entries( &block )
|
196
|
+
rval = self.glob( :recurse => false )
|
197
|
+
|
198
|
+
if block_given?
|
199
|
+
for entry in rval
|
200
|
+
yield entry
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
rval
|
186
205
|
end
|
187
206
|
|
188
207
|
def expand
|
@@ -195,7 +214,7 @@ module FunWith
|
|
195
214
|
#
|
196
215
|
# Takes an options hash as the last argument, allowing same options as FileUtils.touch
|
197
216
|
def touch( *args, &block )
|
198
|
-
args, opts = extract_opts_from_args( args )
|
217
|
+
args, opts = Utils::Opts.extract_opts_from_args( args )
|
199
218
|
|
200
219
|
raise "Cannot create subdirectory to a file" if self.file? && args.length > 0
|
201
220
|
touched = self.join(*args)
|
@@ -210,7 +229,7 @@ module FunWith
|
|
210
229
|
end
|
211
230
|
|
212
231
|
self.touch_dir( dir_for_touched_file, opts ) unless dir_for_touched_file.directory?
|
213
|
-
FileUtils.touch( touched,
|
232
|
+
FileUtils.touch( touched, ** Utils::Opts.narrow_file_utils_options( opts, :touch ) )
|
214
233
|
|
215
234
|
yield touched if block_given?
|
216
235
|
return touched
|
@@ -219,13 +238,13 @@ module FunWith
|
|
219
238
|
# Takes the options of both FileUtils.touch and FileUtils.mkdir_p
|
220
239
|
# mkdir_p options will only matter if the directory is being created.
|
221
240
|
def touch_dir( *args, &block )
|
222
|
-
args, opts = extract_opts_from_args( args )
|
241
|
+
args, opts = Utils::Opts.extract_opts_from_args( args )
|
223
242
|
|
224
243
|
touched = self.join(*args)
|
225
244
|
if touched.directory?
|
226
|
-
FileUtils.touch( touched,
|
245
|
+
FileUtils.touch( touched, ** Utils::Opts.narrow_file_utils_options( opts, :touch ) ) # update access time
|
227
246
|
else
|
228
|
-
FileUtils.mkdir_p( touched,
|
247
|
+
FileUtils.mkdir_p( touched, ** Utils::Opts.narrow_file_utils_options( opts, :mkdir_p ) ) # create directory (and any needed parents)
|
229
248
|
end
|
230
249
|
|
231
250
|
yield touched if block_given?
|
@@ -233,7 +252,7 @@ module FunWith
|
|
233
252
|
end
|
234
253
|
|
235
254
|
def write( *args, &block )
|
236
|
-
args, opts = extract_opts_from_args( args )
|
255
|
+
args, opts = Utils::Opts.extract_opts_from_args( args )
|
237
256
|
|
238
257
|
content = args.first
|
239
258
|
|
@@ -273,6 +292,8 @@ module FunWith
|
|
273
292
|
end
|
274
293
|
end
|
275
294
|
end
|
295
|
+
|
296
|
+
alias :<< :append
|
276
297
|
|
277
298
|
# Returns a [list] of the lines in the file matching the given file. Contrast with
|
278
299
|
|
@@ -291,29 +312,122 @@ module FunWith
|
|
291
312
|
# empty? has different meanings depending on whether you're talking about a file
|
292
313
|
# or a directory. A directory must not have any files or subdirectories. A file
|
293
314
|
# must not have any data in it.
|
294
|
-
def empty?
|
315
|
+
def empty?( &block )
|
295
316
|
raise Exceptions::FileDoesNotExist unless self.exist?
|
296
|
-
|
317
|
+
|
297
318
|
if self.file?
|
298
|
-
File.size( self ) == 0
|
319
|
+
is_empty = File.size( self ) == 0
|
299
320
|
elsif self.directory?
|
300
|
-
|
321
|
+
is_empty = Dir.entries( self ).length <= 2 # Dir.entries returns ".." and "." even when nothing else is there
|
301
322
|
end
|
323
|
+
|
324
|
+
_yield_self_on_success( is_empty, &block )
|
302
325
|
end
|
303
326
|
|
304
327
|
# Does not return a filepath
|
328
|
+
#
|
329
|
+
# TODO: Why not?
|
305
330
|
def basename_no_ext
|
306
331
|
self.basename.to_s.split(".")[0..-2].join(".")
|
307
332
|
end
|
308
|
-
|
309
|
-
|
310
|
-
|
333
|
+
|
334
|
+
#
|
335
|
+
def size( units = :B )
|
336
|
+
sz = self.stat.size
|
337
|
+
|
338
|
+
case units
|
339
|
+
when :B
|
340
|
+
sz
|
341
|
+
when :K
|
342
|
+
(sz / 1024.0).round(1)
|
343
|
+
when :M
|
344
|
+
(sz / 1048576.0).round(1)
|
345
|
+
when :G
|
346
|
+
(sz / 1073741824.0).round(1)
|
347
|
+
end
|
311
348
|
end
|
312
349
|
|
313
|
-
#
|
314
|
-
#
|
315
|
-
#
|
316
|
-
#
|
350
|
+
# Returns the path, stripped of the final extension (.ARG).
|
351
|
+
# The result cannot destroy a dotfile or leave an empty
|
352
|
+
# path
|
353
|
+
# "~/.bashrc".without_ext( .bashrc ) => "~/.bashrc",
|
354
|
+
# if an argument is given, the final ext must match
|
355
|
+
# the given argument, or a copy of the unaltered path
|
356
|
+
# is returned.
|
357
|
+
#
|
358
|
+
# Also:
|
359
|
+
# Don't add a leading ./ when the original didn't have one
|
360
|
+
# Case InSeNSItive, because I can't think of a use case for "only"
|
361
|
+
# strip the extension if the capitalization matches
|
362
|
+
# Chews up any number of leading '.'s
|
363
|
+
# For the moment,
|
364
|
+
def without_ext( ext_val = nil )
|
365
|
+
ext_chopper_regex = if ext_val.fwf_blank?
|
366
|
+
/\.+\w+$/i # any ending "word characters"
|
367
|
+
else
|
368
|
+
# It's okay for the caller to make the leading period explicit
|
369
|
+
ext_val = ext_val.to_s.gsub( /^\./, '' )
|
370
|
+
/\.+#{ext_val}+$/i
|
371
|
+
end
|
372
|
+
|
373
|
+
chopped_str = @path.gsub( ext_chopper_regex, "" )
|
374
|
+
|
375
|
+
do_we_chop = if chopped_str == @path # no change, then sure, why not?
|
376
|
+
true
|
377
|
+
elsif chopped_str.fwf_blank? || chopped_str[-1] == self.separator() || chopped_str[-1] == "."
|
378
|
+
false
|
379
|
+
else
|
380
|
+
true
|
381
|
+
end
|
382
|
+
|
383
|
+
self.class.new( do_we_chop ? chopped_str : @path )
|
384
|
+
|
385
|
+
# # If the results fail to live up to some pre-defined
|
386
|
+
#
|
387
|
+
#
|
388
|
+
#
|
389
|
+
# # do we or don't we?
|
390
|
+
# chop_extension = true
|
391
|
+
#
|
392
|
+
# # Don't if there's an extension mismatch
|
393
|
+
# # Don't if the remainder is a /. (original was a dot_file)
|
394
|
+
#
|
395
|
+
#
|
396
|
+
#
|
397
|
+
#
|
398
|
+
#
|
399
|
+
#
|
400
|
+
# _dirname, _basename, _ext = self.dirname_and_basename_and_ext
|
401
|
+
# debugger if _basename =~ "hello"
|
402
|
+
# ext_val = ext_val.to_s
|
403
|
+
#
|
404
|
+
# # 1) Only perform if the extension match the one given (or was a blank ext given?)
|
405
|
+
#
|
406
|
+
#
|
407
|
+
#
|
408
|
+
# new_path = @path.clone
|
409
|
+
#
|
410
|
+
# current_ext = self.ext
|
411
|
+
#
|
412
|
+
# e = e.to_s
|
413
|
+
#
|
414
|
+
# if e.fwf_present?
|
415
|
+
# new_path.gsub!( /\.#{e}$/, "" )
|
416
|
+
# result = self.gsub(/#{ not_beginning_of_line_or_separator}\.#{self.ext}$/, '')
|
417
|
+
# else
|
418
|
+
# self.clone
|
419
|
+
# end
|
420
|
+
#
|
421
|
+
# self.class.new( new_path )
|
422
|
+
end
|
423
|
+
|
424
|
+
# Two separate modes.
|
425
|
+
#
|
426
|
+
# With no arguments given, returns the current extension as a string (not a filepath). No leading period.
|
427
|
+
#
|
428
|
+
# With an argument, returns the path with a .(arg) tacked onto the end. Result is the same whether a leading
|
429
|
+
# period is given or not. Multiple extensions can be given, and any object can be used so long as it responds
|
430
|
+
# sensibly to .to_s() (use at your own risk). Integers work, for example.
|
317
431
|
def ext( *args )
|
318
432
|
if args.length == 0
|
319
433
|
split_basename = self.basename.to_s.split(".")
|
@@ -329,6 +443,21 @@ module FunWith
|
|
329
443
|
self.class.new( "#{@path}#{appended_to_path}" )
|
330
444
|
end
|
331
445
|
end
|
446
|
+
|
447
|
+
# asks if the .XXX at the end of the path matches one of the extensions given
|
448
|
+
# as an argument. If a block is given, the block will be run if the extension
|
449
|
+
# matches, ignored if no match is detected.
|
450
|
+
def ext?( *extensions, &block )
|
451
|
+
# Why is .to_str used here instead of to_s?
|
452
|
+
acceptable_extensions = extensions.map do |e|
|
453
|
+
ext = e.is_a?( Pathname ) ? e.to_str : e.to_s
|
454
|
+
ext.gsub(/^\./,'')
|
455
|
+
end
|
456
|
+
|
457
|
+
ext_matches = acceptable_extensions.include?( self.ext )
|
458
|
+
|
459
|
+
_yield_self_on_success( ext_matches, &block )
|
460
|
+
end
|
332
461
|
|
333
462
|
# base, ext = @path.basename_and_ext
|
334
463
|
def basename_and_ext
|
@@ -352,8 +481,8 @@ module FunWith
|
|
352
481
|
self.directory? ? self : self.dirname
|
353
482
|
end
|
354
483
|
|
355
|
-
def original?
|
356
|
-
!self.symlink
|
484
|
+
def original?(&block)
|
485
|
+
_yield_self_on_success( !self.symlink?, &block )
|
357
486
|
end
|
358
487
|
|
359
488
|
def original
|
@@ -366,78 +495,58 @@ module FunWith
|
|
366
495
|
self.class.new( dir )
|
367
496
|
end
|
368
497
|
|
369
|
-
def fwf_filepath
|
498
|
+
def fwf_filepath( &block )
|
499
|
+
yield self if block_given?
|
370
500
|
self
|
371
501
|
end
|
372
|
-
|
373
|
-
#
|
374
|
-
#
|
375
|
-
#
|
376
|
-
#
|
377
|
-
#
|
378
|
-
#
|
379
|
-
# You can change the length of the sequence string by passing
|
380
|
-
# in an argument, but it should always be the same value for
|
381
|
-
# a given set of files.
|
502
|
+
|
503
|
+
# Making this stricter, and failing when the formatting doesn't meet expectations, rather than
|
504
|
+
# guessing wildly about what to do on corner cases.
|
505
|
+
#
|
506
|
+
# This file is part of a sequence of files (file.000001.txt, file.000002.txt, file.000003.txt, etc.)
|
507
|
+
# `succ()` gives you the next file in the sequence. If no counter is present, a counter is
|
508
|
+
# added (file.dat --> file.000000.dat)
|
382
509
|
#
|
383
|
-
#
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
510
|
+
# You can change the length of the sequence string by passing in the `digit_count` argument.
|
511
|
+
# Changing the length for a given sequence will lead to the function not recognizing the existing
|
512
|
+
# counter, and installing a new one after it.
|
513
|
+
def succ( digit_count: SUCC_DIGIT_COUNT )
|
514
|
+
directory_pieces = self.split
|
515
|
+
succession_basename = Utils::Succession.get_successor_name( directory_pieces.pop, digit_count )
|
516
|
+
|
517
|
+
return FilePath.new( * directory_pieces ) / succession_basename
|
518
|
+
end
|
519
|
+
|
520
|
+
|
521
|
+
def timestamp( format: :default, time: Time.now, splitter: ".", &block )
|
522
|
+
timestamped_basename = Utils::Timestamp.timestamp( self.basename, format: format, time: time, splitter: splitter )
|
523
|
+
|
524
|
+
directory_path = self.split[0..-2]
|
525
|
+
|
526
|
+
# sigh....
|
527
|
+
if directory_path.first.path == "." && self.path[0..1] != ".#{File::SEPARATOR}"
|
528
|
+
directory_path.shift
|
392
529
|
end
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
if chunks.length == 1
|
397
|
-
if timestamp
|
398
|
-
chunks.push( timestamp )
|
399
|
-
else
|
400
|
-
chunks.push( "0" * digit_count )
|
401
|
-
end
|
402
|
-
# sequence stamp before file extension
|
403
|
-
elsif match_data = chunks[-2].match( /^(\d{#{digit_count}})$/ )
|
404
|
-
if timestamp
|
405
|
-
chunks[-2] = timestamp
|
406
|
-
else
|
407
|
-
i = match_data[1].to_i + 1
|
408
|
-
chunks[-2] = sprintf("%0#{digit_count}i", i)
|
409
|
-
end
|
410
|
-
# try to match sequence stamp to end of filename
|
411
|
-
elsif match_data = chunks[-1].match( /^(\d{#{digit_count}})$/ )
|
412
|
-
if timestamp
|
413
|
-
chunks[-1] = timestamp
|
414
|
-
else
|
415
|
-
i = match_data[1].to_i + 1
|
416
|
-
chunks[-1] = sprintf("%0#{digit_count}i", i)
|
417
|
-
end
|
418
|
-
# not yet sequence_stamped, has file extension
|
530
|
+
|
531
|
+
if directory_path.length == 0
|
532
|
+
timestamped_file = timestamped_basename.fwf_filepath
|
419
533
|
else
|
420
|
-
|
534
|
+
timestamped_file = FilePath.new( * self.split[0..-2], timestamped_basename )
|
421
535
|
end
|
422
|
-
|
423
|
-
|
536
|
+
|
537
|
+
_yield_and_return( timestamped_file, &block )
|
424
538
|
end
|
425
|
-
|
426
|
-
|
427
|
-
def timestamp( format = true, &block )
|
428
|
-
nxt = self.succ( :timestamp => format )
|
429
|
-
yield nxt if block_given?
|
430
|
-
nxt
|
431
|
-
end
|
432
|
-
|
539
|
+
|
433
540
|
# puts a string between the main part of the basename and the extension
|
434
541
|
# or after the basename if there is no extension. Used to describe some
|
435
542
|
# file variant.
|
543
|
+
#
|
436
544
|
# Example "/home/docs/my_awesome_screenplay.txt".fwf_filepath.specifier("final_draft")
|
437
545
|
# => FunWith::Files::FilePath:/home/docs/my_awesome_screenplay.final_draft.txt
|
438
546
|
#
|
439
|
-
#
|
440
|
-
|
547
|
+
# For the purposes of this method, anything after the last period in the filename
|
548
|
+
# is considered the extension.
|
549
|
+
def specifier( str, &block )
|
441
550
|
str = str.to_s
|
442
551
|
chunks = self.to_s.split(".")
|
443
552
|
|
@@ -447,9 +556,13 @@ module FunWith
|
|
447
556
|
chunks = chunks[0..-2] + [str] + [chunks[-1]]
|
448
557
|
end
|
449
558
|
|
450
|
-
chunks.join(".").fwf_filepath
|
559
|
+
f = chunks.join(".").fwf_filepath
|
560
|
+
_yield_and_return( f, &block )
|
451
561
|
end
|
452
|
-
|
562
|
+
|
563
|
+
# Returns the "wildcarded" version of the filename, suitable for finding related files
|
564
|
+
# with similar timestamps.
|
565
|
+
#
|
453
566
|
# TODO: succession : enumerates a sequence of files that get passed
|
454
567
|
# to a block in order.
|
455
568
|
def succession( opts = { digit_count: SUCC_DIGIT_COUNT, timestamp: false } )
|
@@ -507,45 +620,11 @@ module FunWith
|
|
507
620
|
# the failed requirement in order to try it later. Doesn't fail until it goes through
|
508
621
|
# a full loop where none of the required files were successful.
|
509
622
|
def requir
|
510
|
-
|
511
|
-
requirements = self.glob( :recursive => true, :ext => "rb" )
|
512
|
-
successfully_required = 1337 # need to break into initial loop
|
513
|
-
failed_requirements = []
|
514
|
-
error_messages = []
|
515
|
-
|
516
|
-
while requirements.length > 0 && successfully_required > 0
|
517
|
-
successfully_required = 0
|
518
|
-
failed_requirements = []
|
519
|
-
error_messages = []
|
520
|
-
|
521
|
-
for requirement in requirements
|
522
|
-
begin
|
523
|
-
requirement.requir
|
524
|
-
successfully_required += 1
|
525
|
-
rescue Exception => e
|
526
|
-
failed_requirements << requirement
|
527
|
-
error_messages << "Error while requiring #{requirement} : #{e.message} (#{e.class})"
|
528
|
-
end
|
529
|
-
end
|
530
|
-
|
531
|
-
requirements = failed_requirements
|
532
|
-
end
|
533
|
-
|
534
|
-
if failed_requirements.length > 0
|
535
|
-
msg = "requiring directory #{self} failed:\n"
|
536
|
-
for message in error_messages
|
537
|
-
msg << "\n\terror message: #{message}"
|
538
|
-
end
|
539
|
-
|
540
|
-
raise NameError.new(msg)
|
541
|
-
end
|
542
|
-
else
|
543
|
-
require self.expand.gsub( /\.rb$/, '' )
|
544
|
-
end
|
623
|
+
FunWith::Files::Requirements::Manager.require_files( self )
|
545
624
|
end
|
546
|
-
|
547
|
-
def root?
|
548
|
-
self == self.up
|
625
|
+
|
626
|
+
def root?(&block)
|
627
|
+
_yield_self_on_success( self == self.up, &block)
|
549
628
|
end
|
550
629
|
|
551
630
|
def descend( &block )
|
@@ -584,46 +663,53 @@ module FunWith
|
|
584
663
|
# end
|
585
664
|
# # otherwise we're installing a separator
|
586
665
|
# end
|
666
|
+
def separator
|
667
|
+
File::SEPARATOR
|
668
|
+
end
|
587
669
|
|
588
670
|
protected
|
589
671
|
# TODO: Need a separate API for user to call
|
590
|
-
def
|
672
|
+
def must_be_file
|
591
673
|
unless self.file?
|
592
674
|
calling_method = caller[0][/`.*'/][1..-2]
|
593
|
-
raise Errno::
|
675
|
+
raise Errno::EACCES.new( "Can only call FunWith::Files::FilePath##{calling_method}() on an existing file.")
|
594
676
|
end
|
595
677
|
end
|
596
678
|
|
597
|
-
def
|
679
|
+
def must_be_directory
|
598
680
|
unless self.directory?
|
599
681
|
calling_method = caller[0][/`.*'/][1..-2]
|
600
|
-
raise Errno::
|
682
|
+
raise Errno::EACCES.new( "Can only call FunWith::Files::FilePath##{calling_method}() on an existing directory." )
|
601
683
|
end
|
602
684
|
end
|
603
685
|
|
604
|
-
def
|
686
|
+
def must_be_writable
|
605
687
|
unless self.writable?
|
606
688
|
calling_method = caller[0][/`.*'/][1..-2]
|
607
|
-
raise Errno::
|
689
|
+
raise Errno::EACCES.new( "Error in FunWith::Files::FilePath##{calling_method}(): #{@path} not writable." )
|
608
690
|
end
|
609
691
|
end
|
610
|
-
|
611
|
-
|
612
|
-
|
692
|
+
|
693
|
+
# Simplifies some of the block-formed methods
|
694
|
+
def _yield_and_return( obj = self, &block )
|
695
|
+
yield obj if block_given?
|
696
|
+
obj
|
613
697
|
end
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
698
|
+
|
699
|
+
# There are a bunch of methods that interrogate the path, and I want them all to respond to the
|
700
|
+
# same block form. @file.exists?, run the block. @file.writable?, run the block. In all cases,
|
701
|
+
# the path is yielded as an argument
|
702
|
+
def _yield_self_on_success( success, &block )
|
703
|
+
if block_given?
|
704
|
+
if success
|
705
|
+
yield self
|
706
|
+
else
|
707
|
+
false
|
708
|
+
end
|
618
709
|
else
|
619
|
-
|
710
|
+
success
|
620
711
|
end
|
621
712
|
end
|
622
|
-
|
623
|
-
def yield_and_return( obj, &block )
|
624
|
-
yield obj if block_given?
|
625
|
-
obj
|
626
|
-
end
|
627
713
|
end
|
628
714
|
end
|
629
715
|
end
|
@@ -15,11 +15,32 @@ module FunWith
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def config_dir( *args )
|
18
|
-
|
18
|
+
xdg_get('CONFIG').fwf_filepath.join( *args )
|
19
19
|
end
|
20
20
|
|
21
21
|
def data_dir( *args )
|
22
|
-
|
22
|
+
xdg_get('DATA').fwf_filepath.join( *args )
|
23
|
+
end
|
24
|
+
|
25
|
+
def cache_dir( *args )
|
26
|
+
xdg_get('CACHE_HOME').fwf_filepath.join( *args )
|
27
|
+
end
|
28
|
+
|
29
|
+
def xdg_get( str )
|
30
|
+
if XDG.respond_to?( :"[]" )
|
31
|
+
XDG[str]
|
32
|
+
else
|
33
|
+
case str
|
34
|
+
when "CONFIG"
|
35
|
+
XDG::Environment.new.config_home
|
36
|
+
when "DATA"
|
37
|
+
XDG::Environment.new.data_home
|
38
|
+
when "CACHE_HOME"
|
39
|
+
XDG::Environment.new.cache_home
|
40
|
+
else
|
41
|
+
raise "Not sure what to do with XDG:#{str}"
|
42
|
+
end
|
43
|
+
end
|
23
44
|
end
|
24
45
|
|
25
46
|
# Honestly this is a token attempt at Windows compatibility.
|
@@ -2,20 +2,31 @@ module FunWith
|
|
2
2
|
module Files
|
3
3
|
# view and change file permissions
|
4
4
|
module FilePermissionMethods
|
5
|
-
def readable?
|
6
|
-
File.readable?( self )
|
5
|
+
def readable?( &block )
|
6
|
+
_yield_self_on_success( File.readable?( self ), &block )
|
7
7
|
end
|
8
8
|
|
9
|
-
def writable?
|
10
|
-
File.writable?( self )
|
9
|
+
def writable?( &block )
|
10
|
+
_yield_self_on_success( File.writable?( self ), &block )
|
11
11
|
end
|
12
12
|
|
13
|
-
def executable?
|
14
|
-
File.executable?( self )
|
13
|
+
def executable?( &block )
|
14
|
+
_yield_self_on_success( File.executable?( self ), &block )
|
15
15
|
end
|
16
16
|
|
17
|
+
# options: :noop, :verbose
|
17
18
|
def chmod( mode, opts = {} )
|
18
|
-
FileUtils.chmod( mode, self,
|
19
|
+
FileUtils.chmod( mode, self, ** Utils::Opts.narrow_file_utils_options( opts, :chmod ) )
|
20
|
+
end
|
21
|
+
|
22
|
+
# options: :noop, :verbose
|
23
|
+
def chown( user, opts = {} )
|
24
|
+
FileUtils.chown( user, self, ** Utils::Opts.narrow_file_utils_options( opts, :chown ) )
|
25
|
+
end
|
26
|
+
|
27
|
+
def owner
|
28
|
+
uid = File.stat( self ).uid
|
29
|
+
Etc.getpwuid( uid ).name
|
19
30
|
end
|
20
31
|
end
|
21
32
|
end
|