fun_with_files 0.0.14 → 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 (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