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.
Files changed (50) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.markdown +15 -3
  3. data/Gemfile +17 -7
  4. data/{README.rdoc → README.markdown} +11 -10
  5. data/VERSION +1 -1
  6. data/lib/fun_with/files/bootstrapper.rb +87 -0
  7. data/lib/fun_with/files/digest_methods.rb +30 -16
  8. data/lib/fun_with/files/directory_builder.rb +4 -0
  9. data/lib/fun_with/files/downloader.rb +3 -19
  10. data/lib/fun_with/files/errors.rb +9 -1
  11. data/lib/fun_with/files/file_manipulation_methods.rb +25 -15
  12. data/lib/fun_with/files/file_path.rb +147 -150
  13. data/lib/fun_with/files/file_path_class_methods.rb +23 -2
  14. data/lib/fun_with/files/file_permission_methods.rb +18 -7
  15. data/lib/fun_with/files/file_requirements.rb +63 -7
  16. data/lib/fun_with/files/requirements/manager.rb +104 -0
  17. data/lib/fun_with/files/root_path.rb +3 -3
  18. data/lib/fun_with/files/stat_methods.rb +33 -0
  19. data/lib/fun_with/files/string_behavior.rb +6 -2
  20. data/lib/fun_with/files/utils/byte_size.rb +143 -0
  21. data/lib/fun_with/files/utils/opts.rb +26 -0
  22. data/lib/fun_with/files/utils/succession.rb +47 -0
  23. data/lib/fun_with/files/utils/timestamp.rb +47 -0
  24. data/lib/fun_with/files/utils/timestamp_format.rb +31 -0
  25. data/lib/fun_with/files/watcher.rb +157 -0
  26. data/lib/fun_with/files/watchers/directory_watcher.rb +67 -0
  27. data/lib/fun_with/files/watchers/file_watcher.rb +45 -0
  28. data/lib/fun_with/files/watchers/missing_watcher.rb +23 -0
  29. data/lib/fun_with/files/watchers/node_watcher.rb +44 -0
  30. data/lib/fun_with/testing/assertions/fun_with_files.rb +91 -0
  31. data/lib/fun_with/testing/test_case_extensions.rb +12 -0
  32. data/lib/fun_with_files.rb +5 -75
  33. data/test/helper.rb +13 -5
  34. data/test/test_core_extensions.rb +5 -0
  35. data/test/test_directory_builder.rb +29 -10
  36. data/test/test_extension_methods.rb +62 -0
  37. data/test/test_file_manipulation.rb +2 -2
  38. data/test/test_file_path.rb +18 -39
  39. data/test/test_file_requirements.rb +36 -0
  40. data/test/test_fun_with_files.rb +1 -1
  41. data/test/test_fwf_assertions.rb +62 -0
  42. data/test/test_moving_files.rb +111 -0
  43. data/test/test_permission_methods.rb +22 -0
  44. data/test/test_root_path.rb +9 -0
  45. data/test/test_stat_methods.rb +17 -0
  46. data/test/test_timestamping.rb +74 -0
  47. data/test/test_utils_bytesize.rb +71 -0
  48. data/test/test_utils_succession.rb +30 -0
  49. data/test/test_watchers.rb +196 -0
  50. 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
- yield joined_path if block_given?
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, narrow_options( opts, FileUtils::OPT_TABLE["touch"] ) )
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, narrow_options( opts, FileUtils::OPT_TABLE["touch"] ) ) # update access time
245
+ FileUtils.touch( touched, ** Utils::Opts.narrow_file_utils_options( opts, :touch ) ) # update access time
239
246
  else
240
- FileUtils.mkdir_p( touched, narrow_options( opts, FileUtils::OPT_TABLE["mkdir_p"] ) ) # create directory (and any needed parents)
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
- self.glob( :all ).fwf_blank?
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. With no arguments given, returns the current extension as a string (not a filepath)
400
- # With an argument, returns the path with a .(arg) tacked onto the end. The leading period is wholly optional.
401
- # Does not return a filepath.
402
- # Does not include leading period
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
- # Gives a sequence of files. Examples:
460
- # file.dat --> file.000000.dat
461
- # file_without_ext --> file_without_ext.000000
462
- # If it sees a six-digit number at or near the end of the
463
- # filename, it increments it.
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
- # TODO: Need to get this relying on the specifier() method.
470
- def succ( opts = { digit_count: SUCC_DIGIT_COUNT, timestamp: false } )
471
- if timestamp = opts[:timestamp]
472
- timestamp_format = timestamp.is_a?(String) ? timestamp : DEFAULT_TIMESTAMP_FORMAT
473
- timestamp = Time.now.strftime( timestamp_format )
474
- digit_count = timestamp.length
475
- else
476
- timestamp = false
477
- digit_count = opts[:digit_count]
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
- chunks = self.basename.to_s.split(".")
481
- # not yet sequence stamped, no file extension.
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
- chunks = [chunks[0..-2], (timestamp ? timestamp : "0" * digit_count), chunks[-1]].flatten
534
+ timestamped_file = FilePath.new( * self.split[0..-2], timestamped_basename )
507
535
  end
508
-
509
- self.up.join( chunks.join(".") )
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
- # Oh hush. *I* find it useful.
526
- def specifier( str )
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
- if self.directory?
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 _must_be_a_file
672
+ def must_be_file
680
673
  unless self.file?
681
674
  calling_method = caller[0][/`.*'/][1..-2]
682
- raise Errno::EACCESS.new( "Can only call FunWith::Files::FilePath##{calling_method}() on an existing file.")
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 _must_be_a_directory
679
+ def must_be_directory
687
680
  unless self.directory?
688
681
  calling_method = caller[0][/`.*'/][1..-2]
689
- raise Errno::EACCESS.new( "Can only call FunWith::Files::FilePath##{calling_method}() on an existing directory.")
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 _must_be_writable
686
+ def must_be_writable
694
687
  unless self.writable?
695
688
  calling_method = caller[0][/`.*'/][1..-2]
696
- raise Errno::EACCESS.new( "Error in FunWith::Files::FilePath##{calling_method}(): #{@path} not writable.")
689
+ raise Errno::EACCES.new( "Error in FunWith::Files::FilePath##{calling_method}(): #{@path} not writable." )
697
690
  end
698
691
  end
699
-
700
- def narrow_options( opts, keys )
701
- opts.keep_if{ |k,v| keys.include?( k ) }
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
- def extract_opts_from_args( args )
705
- if args.last.is_a?( Hash )
706
- [args[0..-2], args.last ]
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
- [args, {}]
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
- XDG['CONFIG'].fwf_filepath.join( *args )
18
+ xdg_get('CONFIG').fwf_filepath.join( *args )
19
19
  end
20
20
 
21
21
  def data_dir( *args )
22
- XDG['DATA'].fwf_filepath.join( *args )
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, narrow_options( opts, FileUtils::OPT_TABLE["chmod"] ) )
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 _raise_error_if_not test, msg, error_class
5
- if test
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
- _raise_error_if_not self.writable?, error_msg, Errno::ENOENT
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 needs_to_be_empty error_msg = "Path needs to point to"
19
- _raise_error_if_not self.empty?, error_msg, Errno::ENOENT
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