fun_with_files 0.0.15 → 0.0.18

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