fun_with_files 0.0.15 → 0.0.18
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 +5 -5
- data/CHANGELOG.markdown +15 -3
- data/Gemfile +17 -7
- data/{README.rdoc → README.markdown} +11 -10
- data/VERSION +1 -1
- data/lib/fun_with/files/bootstrapper.rb +87 -0
- data/lib/fun_with/files/digest_methods.rb +30 -16
- data/lib/fun_with/files/directory_builder.rb +4 -0
- data/lib/fun_with/files/downloader.rb +3 -19
- 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_path.rb +147 -150
- 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 -75
- data/test/helper.rb +13 -5
- data/test/test_core_extensions.rb +5 -0
- data/test/test_directory_builder.rb +29 -10
- data/test/test_extension_methods.rb +62 -0
- data/test/test_file_manipulation.rb +2 -2
- data/test/test_file_path.rb +18 -39
- 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 +54 -16
@@ -2,10 +2,10 @@ module FunWith
|
|
2
2
|
module Files
|
3
3
|
class FilePath < Pathname
|
4
4
|
SUCC_DIGIT_COUNT = 6
|
5
|
-
DEFAULT_TIMESTAMP_FORMAT = "%Y%m%d%H%M%S%L"
|
6
5
|
|
7
|
-
def initialize( *args )
|
8
|
-
super( File.join( *args ) )
|
6
|
+
def initialize( *args, &block )
|
7
|
+
super( File.join( *(args.map(&:to_s) ) ) )
|
8
|
+
_yield_and_return( self, &block )
|
9
9
|
end
|
10
10
|
|
11
11
|
attr_accessor :path
|
@@ -40,30 +40,29 @@ module FunWith
|
|
40
40
|
|
41
41
|
def join( *args, &block )
|
42
42
|
joined_path = self.class.new( super( *(args.map(&:to_s) ) ) )
|
43
|
-
|
44
|
-
joined_path
|
43
|
+
_yield_and_return( joined_path, &block )
|
45
44
|
end
|
46
45
|
|
47
46
|
def join!( *args, &block )
|
48
47
|
@path = self.join( *args, &block ).to_str
|
49
|
-
self
|
48
|
+
_yield_and_return( self, &block )
|
50
49
|
end
|
51
50
|
|
52
|
-
def / arg
|
53
|
-
self.join( arg )
|
51
|
+
def /( arg, &block )
|
52
|
+
_yield_and_return( self.join( arg ), &block )
|
54
53
|
end
|
55
54
|
|
56
|
-
def [] *args
|
57
|
-
self.join(*args)
|
55
|
+
def []( *args, &block )
|
56
|
+
_yield_and_return( self.join(*args), &block )
|
58
57
|
end
|
59
58
|
|
60
59
|
|
61
|
-
def doesnt_exist?
|
62
|
-
self.exist? == false
|
60
|
+
def doesnt_exist?( &block )
|
61
|
+
_yield_self_on_success( self.exist? == false, &block )
|
63
62
|
end
|
64
63
|
|
65
|
-
def not_a_file?
|
66
|
-
! self.file
|
64
|
+
def not_a_file?( &block )
|
65
|
+
_yield_self_on_success( ! self.file?, &block )
|
67
66
|
end
|
68
67
|
|
69
68
|
alias :absent? :doesnt_exist?
|
@@ -193,8 +192,16 @@ module FunWith
|
|
193
192
|
end
|
194
193
|
end
|
195
194
|
|
196
|
-
def entries
|
197
|
-
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
|
198
205
|
end
|
199
206
|
|
200
207
|
def expand
|
@@ -207,7 +214,7 @@ module FunWith
|
|
207
214
|
#
|
208
215
|
# Takes an options hash as the last argument, allowing same options as FileUtils.touch
|
209
216
|
def touch( *args, &block )
|
210
|
-
args, opts = extract_opts_from_args( args )
|
217
|
+
args, opts = Utils::Opts.extract_opts_from_args( args )
|
211
218
|
|
212
219
|
raise "Cannot create subdirectory to a file" if self.file? && args.length > 0
|
213
220
|
touched = self.join(*args)
|
@@ -222,7 +229,7 @@ module FunWith
|
|
222
229
|
end
|
223
230
|
|
224
231
|
self.touch_dir( dir_for_touched_file, opts ) unless dir_for_touched_file.directory?
|
225
|
-
FileUtils.touch( touched,
|
232
|
+
FileUtils.touch( touched, ** Utils::Opts.narrow_file_utils_options( opts, :touch ) )
|
226
233
|
|
227
234
|
yield touched if block_given?
|
228
235
|
return touched
|
@@ -231,13 +238,13 @@ module FunWith
|
|
231
238
|
# Takes the options of both FileUtils.touch and FileUtils.mkdir_p
|
232
239
|
# mkdir_p options will only matter if the directory is being created.
|
233
240
|
def touch_dir( *args, &block )
|
234
|
-
args, opts = extract_opts_from_args( args )
|
241
|
+
args, opts = Utils::Opts.extract_opts_from_args( args )
|
235
242
|
|
236
243
|
touched = self.join(*args)
|
237
244
|
if touched.directory?
|
238
|
-
FileUtils.touch( touched,
|
245
|
+
FileUtils.touch( touched, ** Utils::Opts.narrow_file_utils_options( opts, :touch ) ) # update access time
|
239
246
|
else
|
240
|
-
FileUtils.mkdir_p( touched,
|
247
|
+
FileUtils.mkdir_p( touched, ** Utils::Opts.narrow_file_utils_options( opts, :mkdir_p ) ) # create directory (and any needed parents)
|
241
248
|
end
|
242
249
|
|
243
250
|
yield touched if block_given?
|
@@ -245,7 +252,7 @@ module FunWith
|
|
245
252
|
end
|
246
253
|
|
247
254
|
def write( *args, &block )
|
248
|
-
args, opts = extract_opts_from_args( args )
|
255
|
+
args, opts = Utils::Opts.extract_opts_from_args( args )
|
249
256
|
|
250
257
|
content = args.first
|
251
258
|
|
@@ -305,14 +312,16 @@ module FunWith
|
|
305
312
|
# empty? has different meanings depending on whether you're talking about a file
|
306
313
|
# or a directory. A directory must not have any files or subdirectories. A file
|
307
314
|
# must not have any data in it.
|
308
|
-
def empty?
|
315
|
+
def empty?( &block )
|
309
316
|
raise Exceptions::FileDoesNotExist unless self.exist?
|
310
|
-
|
317
|
+
|
311
318
|
if self.file?
|
312
|
-
File.size( self ) == 0
|
319
|
+
is_empty = File.size( self ) == 0
|
313
320
|
elsif self.directory?
|
314
|
-
|
321
|
+
is_empty = Dir.entries( self ).length <= 2 # Dir.entries returns ".." and "." even when nothing else is there
|
315
322
|
end
|
323
|
+
|
324
|
+
_yield_self_on_success( is_empty, &block )
|
316
325
|
end
|
317
326
|
|
318
327
|
# Does not return a filepath
|
@@ -321,6 +330,22 @@ module FunWith
|
|
321
330
|
def basename_no_ext
|
322
331
|
self.basename.to_s.split(".")[0..-2].join(".")
|
323
332
|
end
|
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
|
348
|
+
end
|
324
349
|
|
325
350
|
# Returns the path, stripped of the final extension (.ARG).
|
326
351
|
# The result cannot destroy a dotfile or leave an empty
|
@@ -396,10 +421,13 @@ module FunWith
|
|
396
421
|
# self.class.new( new_path )
|
397
422
|
end
|
398
423
|
|
399
|
-
# Two separate modes.
|
400
|
-
#
|
401
|
-
#
|
402
|
-
#
|
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.
|
403
431
|
def ext( *args )
|
404
432
|
if args.length == 0
|
405
433
|
split_basename = self.basename.to_s.split(".")
|
@@ -415,6 +443,21 @@ module FunWith
|
|
415
443
|
self.class.new( "#{@path}#{appended_to_path}" )
|
416
444
|
end
|
417
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
|
418
461
|
|
419
462
|
# base, ext = @path.basename_and_ext
|
420
463
|
def basename_and_ext
|
@@ -438,8 +481,8 @@ module FunWith
|
|
438
481
|
self.directory? ? self : self.dirname
|
439
482
|
end
|
440
483
|
|
441
|
-
def original?
|
442
|
-
!self.symlink
|
484
|
+
def original?(&block)
|
485
|
+
_yield_self_on_success( !self.symlink?, &block )
|
443
486
|
end
|
444
487
|
|
445
488
|
def original
|
@@ -452,78 +495,58 @@ module FunWith
|
|
452
495
|
self.class.new( dir )
|
453
496
|
end
|
454
497
|
|
455
|
-
def fwf_filepath
|
498
|
+
def fwf_filepath( &block )
|
499
|
+
yield self if block_given?
|
456
500
|
self
|
457
501
|
end
|
458
|
-
|
459
|
-
#
|
460
|
-
#
|
461
|
-
#
|
462
|
-
#
|
463
|
-
#
|
464
|
-
#
|
465
|
-
# You can change the length of the sequence string by passing
|
466
|
-
# in an argument, but it should always be the same value for
|
467
|
-
# 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)
|
468
509
|
#
|
469
|
-
#
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
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
|
478
529
|
end
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
if chunks.length == 1
|
483
|
-
if timestamp
|
484
|
-
chunks.push( timestamp )
|
485
|
-
else
|
486
|
-
chunks.push( "0" * digit_count )
|
487
|
-
end
|
488
|
-
# sequence stamp before file extension
|
489
|
-
elsif match_data = chunks[-2].match( /^(\d{#{digit_count}})$/ )
|
490
|
-
if timestamp
|
491
|
-
chunks[-2] = timestamp
|
492
|
-
else
|
493
|
-
i = match_data[1].to_i + 1
|
494
|
-
chunks[-2] = sprintf("%0#{digit_count}i", i)
|
495
|
-
end
|
496
|
-
# try to match sequence stamp to end of filename
|
497
|
-
elsif match_data = chunks[-1].match( /^(\d{#{digit_count}})$/ )
|
498
|
-
if timestamp
|
499
|
-
chunks[-1] = timestamp
|
500
|
-
else
|
501
|
-
i = match_data[1].to_i + 1
|
502
|
-
chunks[-1] = sprintf("%0#{digit_count}i", i)
|
503
|
-
end
|
504
|
-
# not yet sequence_stamped, has file extension
|
530
|
+
|
531
|
+
if directory_path.length == 0
|
532
|
+
timestamped_file = timestamped_basename.fwf_filepath
|
505
533
|
else
|
506
|
-
|
534
|
+
timestamped_file = FilePath.new( * self.split[0..-2], timestamped_basename )
|
507
535
|
end
|
508
|
-
|
509
|
-
|
510
|
-
end
|
511
|
-
|
512
|
-
|
513
|
-
def timestamp( format = true, &block )
|
514
|
-
nxt = self.succ( :timestamp => format )
|
515
|
-
yield nxt if block_given?
|
516
|
-
nxt
|
536
|
+
|
537
|
+
_yield_and_return( timestamped_file, &block )
|
517
538
|
end
|
518
|
-
|
539
|
+
|
519
540
|
# puts a string between the main part of the basename and the extension
|
520
541
|
# or after the basename if there is no extension. Used to describe some
|
521
542
|
# file variant.
|
543
|
+
#
|
522
544
|
# Example "/home/docs/my_awesome_screenplay.txt".fwf_filepath.specifier("final_draft")
|
523
545
|
# => FunWith::Files::FilePath:/home/docs/my_awesome_screenplay.final_draft.txt
|
524
546
|
#
|
525
|
-
#
|
526
|
-
|
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 )
|
527
550
|
str = str.to_s
|
528
551
|
chunks = self.to_s.split(".")
|
529
552
|
|
@@ -533,9 +556,13 @@ module FunWith
|
|
533
556
|
chunks = chunks[0..-2] + [str] + [chunks[-1]]
|
534
557
|
end
|
535
558
|
|
536
|
-
chunks.join(".").fwf_filepath
|
559
|
+
f = chunks.join(".").fwf_filepath
|
560
|
+
_yield_and_return( f, &block )
|
537
561
|
end
|
538
|
-
|
562
|
+
|
563
|
+
# Returns the "wildcarded" version of the filename, suitable for finding related files
|
564
|
+
# with similar timestamps.
|
565
|
+
#
|
539
566
|
# TODO: succession : enumerates a sequence of files that get passed
|
540
567
|
# to a block in order.
|
541
568
|
def succession( opts = { digit_count: SUCC_DIGIT_COUNT, timestamp: false } )
|
@@ -593,45 +620,11 @@ module FunWith
|
|
593
620
|
# the failed requirement in order to try it later. Doesn't fail until it goes through
|
594
621
|
# a full loop where none of the required files were successful.
|
595
622
|
def requir
|
596
|
-
|
597
|
-
requirements = self.glob( :recursive => true, :ext => "rb" )
|
598
|
-
successfully_required = 1337 # need to break into initial loop
|
599
|
-
failed_requirements = []
|
600
|
-
error_messages = []
|
601
|
-
|
602
|
-
while requirements.length > 0 && successfully_required > 0
|
603
|
-
successfully_required = 0
|
604
|
-
failed_requirements = []
|
605
|
-
error_messages = []
|
606
|
-
|
607
|
-
for requirement in requirements
|
608
|
-
begin
|
609
|
-
requirement.requir
|
610
|
-
successfully_required += 1
|
611
|
-
rescue Exception => e
|
612
|
-
failed_requirements << requirement
|
613
|
-
error_messages << "Error while requiring #{requirement} : #{e.message} (#{e.class})"
|
614
|
-
end
|
615
|
-
end
|
616
|
-
|
617
|
-
requirements = failed_requirements
|
618
|
-
end
|
619
|
-
|
620
|
-
if failed_requirements.length > 0
|
621
|
-
msg = "requiring directory #{self} failed:\n"
|
622
|
-
for message in error_messages
|
623
|
-
msg << "\n\terror message: #{message}"
|
624
|
-
end
|
625
|
-
|
626
|
-
raise NameError.new(msg)
|
627
|
-
end
|
628
|
-
else
|
629
|
-
require self.expand.gsub( /\.rb$/, '' )
|
630
|
-
end
|
623
|
+
FunWith::Files::Requirements::Manager.require_files( self )
|
631
624
|
end
|
632
|
-
|
633
|
-
def root?
|
634
|
-
self == self.up
|
625
|
+
|
626
|
+
def root?(&block)
|
627
|
+
_yield_self_on_success( self == self.up, &block)
|
635
628
|
end
|
636
629
|
|
637
630
|
def descend( &block )
|
@@ -676,43 +669,47 @@ module FunWith
|
|
676
669
|
|
677
670
|
protected
|
678
671
|
# TODO: Need a separate API for user to call
|
679
|
-
def
|
672
|
+
def must_be_file
|
680
673
|
unless self.file?
|
681
674
|
calling_method = caller[0][/`.*'/][1..-2]
|
682
|
-
raise Errno::
|
675
|
+
raise Errno::EACCES.new( "Can only call FunWith::Files::FilePath##{calling_method}() on an existing file.")
|
683
676
|
end
|
684
677
|
end
|
685
678
|
|
686
|
-
def
|
679
|
+
def must_be_directory
|
687
680
|
unless self.directory?
|
688
681
|
calling_method = caller[0][/`.*'/][1..-2]
|
689
|
-
raise Errno::
|
682
|
+
raise Errno::EACCES.new( "Can only call FunWith::Files::FilePath##{calling_method}() on an existing directory." )
|
690
683
|
end
|
691
684
|
end
|
692
685
|
|
693
|
-
def
|
686
|
+
def must_be_writable
|
694
687
|
unless self.writable?
|
695
688
|
calling_method = caller[0][/`.*'/][1..-2]
|
696
|
-
raise Errno::
|
689
|
+
raise Errno::EACCES.new( "Error in FunWith::Files::FilePath##{calling_method}(): #{@path} not writable." )
|
697
690
|
end
|
698
691
|
end
|
699
|
-
|
700
|
-
|
701
|
-
|
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
|
702
697
|
end
|
703
|
-
|
704
|
-
|
705
|
-
|
706
|
-
|
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
|
707
709
|
else
|
708
|
-
|
710
|
+
success
|
709
711
|
end
|
710
712
|
end
|
711
|
-
|
712
|
-
def yield_and_return( obj, &block )
|
713
|
-
yield obj if block_given?
|
714
|
-
obj
|
715
|
-
end
|
716
713
|
end
|
717
714
|
end
|
718
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
|
@@ -1,22 +1,78 @@
|
|
1
1
|
module FunWith
|
2
2
|
module Files
|
3
3
|
module FileRequirements
|
4
|
-
def
|
5
|
-
|
6
|
-
raise error_class.new( msg + "(file: #{self})" )
|
7
|
-
end
|
4
|
+
def needs_to_exist error_msg = "Path does not exist"
|
5
|
+
_raise_error_if_not self.exist?, error_msg, Errno::ENOENT
|
8
6
|
end
|
9
7
|
|
10
8
|
def needs_to_be_a_file error_msg = "Path is not a file"
|
9
|
+
self.needs_to_exist( error_msg + " (does not exist)" )
|
11
10
|
_raise_error_if_not self.file?, error_msg, Errno::ENOENT
|
12
11
|
end
|
13
12
|
|
13
|
+
def needs_to_be_readable error_msg = "Path is not readable"
|
14
|
+
self.needs_to_exist( error_msg + " (does not exist)" )
|
15
|
+
_raise_error_if_not self.writable?, error_msg, Errno::EPERM
|
16
|
+
end
|
17
|
+
|
14
18
|
def needs_to_be_writable error_msg = "Path is not writable"
|
15
|
-
|
19
|
+
self.needs_to_exist( error_msg + " (does not exist)" )
|
20
|
+
_raise_error_if_not self.writable?, error_msg, Errno::EPERM
|
16
21
|
end
|
17
22
|
|
18
|
-
def
|
19
|
-
|
23
|
+
def needs_to_be_executable error_msg = "Path is not executable"
|
24
|
+
self.needs_to_exist( error_msg + " (does not exist)" )
|
25
|
+
_raise_error_if_not self.executable?, error_msg, Errno::ENOEXEC
|
26
|
+
end
|
27
|
+
|
28
|
+
# returns a different code depending on whether the path is a file
|
29
|
+
# or a directory.
|
30
|
+
def needs_to_be_empty error_msg = "Path needs to be empty"
|
31
|
+
self.needs_to_exist( error_msg + " (does not exist)" )
|
32
|
+
error_class = Errno::EOWNERDEAD # it's as good a code as any
|
33
|
+
error_class = Errno::ENOTEMPTY if self.directory?
|
34
|
+
error_class = Errors::FileNotEmpty if self.file? # there's no libc error for "file oughta be empty"
|
35
|
+
|
36
|
+
_raise_error_if_not self.empty?, error_msg, error_class
|
37
|
+
end
|
38
|
+
|
39
|
+
def needs_to_be_a_directory error_msg = "Path is not a directory"
|
40
|
+
self.needs_to_exist( error_msg + " (does not exist)" )
|
41
|
+
_raise_error_if_not self.directory?, error_msg, Errno::ENOTDIR
|
42
|
+
end
|
43
|
+
|
44
|
+
def needs_to_be( *requirements )
|
45
|
+
for requirement in requirements
|
46
|
+
case requirement
|
47
|
+
when :exist
|
48
|
+
self.needs_to_exist
|
49
|
+
when :readable
|
50
|
+
self.needs_to_be_readable
|
51
|
+
when :writable
|
52
|
+
self.needs_to_be_writable
|
53
|
+
when :executable
|
54
|
+
self.needs_to_be_executable
|
55
|
+
when :empty
|
56
|
+
self.needs_to_be_empty
|
57
|
+
when :directory
|
58
|
+
self.needs_to_be_a_directory
|
59
|
+
when :file
|
60
|
+
self.needs_to_be_a_file
|
61
|
+
else
|
62
|
+
warn "Did not understand file.needs_to_be constraint: #{arg}"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
protected
|
68
|
+
def _raise_error_if test, msg, error_class
|
69
|
+
if test
|
70
|
+
raise error_class.new( msg + "(file: #{self})" )
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def _raise_error_if_not test, msg, error_class
|
75
|
+
_raise_error_if !test, msg, error_class
|
20
76
|
end
|
21
77
|
end
|
22
78
|
end
|