fun_with_files 0.0.15 → 0.0.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
         |