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.
- 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
|