require 'fileutils'


module Ratch

  module FileUtils

    # Glob files.

    def glob(*args, &blk)
      Dir.glob(*args, &blk)
    end

    # Read file.

    def file_read(path)
      File.read(path)
    end

    # Write file.

    def file_write(path, text)
      if dryrun?
        puts "write #{path}"
      else
        File.open(path, 'w'){ |f| f << text }
      end
    end

    ##########################
    # Add FileUtils Features #
    ##########################

    ::FileUtils.private_instance_methods(false).each do |meth|
      next if meth =~ /^fu_/
      module_eval %{
        def #{meth}(*a,&b)
          fileutils.#{meth}(*a,&b)
        end
      }
    end

    # Delegate access to FileUtils.

    def fileutils
      dryrun? ? ::FileUtils::DryRun : ::FileUtils
    end

    # Bonus FileUtils features.

    def cd(*a,&b)
      puts "cd #{a}" if dryrun?
      fileutils.chdir(*a,&b)
    end


    #########################
    # Add FileTest Features #
    #########################

    FileTest.private_instance_methods(false).each do |meth|
      next if meth =~ /^fu_/
      module_eval %{
        def #{meth}(*a,&b)
          FileTest.#{meth}(*a,&b)
        end
      }
    end

    # Is a given path a regular file? If +path+ is a glob
    # then checks to see if all matches are refular files.

    def file?(path)
      paths = Dir.glob(path)
      paths.not_empty? && paths.all?{ |f| FileTest.file?(f) }
    end

    # Assert that a given path is a file.

    def file!(*paths)
      abort "file not found #{path}" unless paths.any?{|path| file?(path)}
    end

    # Is a given path a directory? If +path+ is a glob
    # checks to see if all matches are directories.

    def dir?(path)
      paths = Dir.glob(path)
      paths.not_empty? && paths.all?{ |f| FileTest.directory?(f) }
    end
    alias_method :directory?, :dir? ; module_function :directory?

    # Assert that a given path is a directory.

    def dir!(*paths)
      paths.each do |path|
        abort "Directory not found: '#{path}'." unless  dir?(path)
      end
    end
    alias_method :directory!, :dir! ; module_function :directory!

#     # Okay, I'm being a dork, but 'fold' seems like a better word
#     # then 'dir', 'folder', or 'directory'.
#
#     def fold?(path)
#       paths = Dir.glob(path)
#       paths.not_empty? && paths.all?{ |f| FileTest.directory?(f) }
#     end
#
#     # Assert that a given path is a fold (ie. a folder).
#
#     def fold!(*paths)
#       abort "fold not found #{path}" unless paths.any?{|path| fold?(path)}
#     end

    # Assert that a path exists.

    def exists?(path)
      paths = Dir.glob(path)
      paths.not_empty?
    end
    alias_method :exist?, :exists? ; module_function :exist?
    alias_method :path?,  :exists? ; module_function :path?

    # Assert that a path exists.

    def exists!(*paths)
      abort "path not found #{path}" unless paths.any?{|path| exists?(path)}
    end
    alias_method :exist!, :exists! ; module_function :exist!
    alias_method :path!,  :exists! ; module_function :path!

    # Is a file a task?

    def task?(path)
      task = File.dirname($0) + "/#{path}"
      task.chomp!('!')
      task if FileTest.file?(task) && FileTest.executable?(task)
    end

    # Is a file a command executable?
    #
    # TODO Probably needs to be fixed for Windows.

    def bin?(path)
      is_bin = command_paths.any? do |f|
        FileTest.exist?(File.join(f, path))
      end
      is_bin ? File.basename(path) : false
    end

    # This is a support method of #bin?

    def command_paths
      @command_paths ||= ENV['PATH'].split(':')
    end

    # TODO Make more robust.

    UNSAFE = [ '/', '/*', '/**/*' ]

    # Is a path considered reasonably "safe"?

    def safe?(path)
      case path
      when *UNSAFE
        return false
      end
      true
    end

  end
end