fun_with_files 0.0.14 → 0.0.18

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