Skip to content
This repository
Fetching contributors…

Cannot retrieve contributors at this time

file 126 lines (107 sloc) 3.353 kb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126
require 'set'

module Listen

  # The dependency-manager offers a simple DSL which allows
  # classes to declare their gem dependencies and load them when
  # needed.
  # It raises a user-friendly exception when the dependencies
  # can't be loaded which has the install command in the message.
  #
  module DependencyManager

    GEM_LOAD_MESSAGE = <<-EOS.gsub(/^ {6}/, '')
Missing dependency '%s' (version '%s')!
EOS

    GEM_INSTALL_COMMAND = <<-EOS.gsub(/^ {6}/, '')
Please run the following to satisfy the dependency:
gem install --version '%s' %s
EOS

    BUNDLER_DECLARE_GEM = <<-EOS.gsub(/^ {6}/, '')
Please add the following to your Gemfile to satisfy the dependency:
gem '%s', '%s'
EOS

    Dependency = Struct.new(:name, :version)

    # The error raised when a dependency can't be loaded.
    class Error < StandardError; end

    # A list of all loaded dependencies in the dependency manager.
    @_loaded_dependencies = Set.new

    # class methods
    class << self

      # Initializes the extended class.
      #
      # @param [Class] the class for which some dependencies must be managed
      #
      def extended(base)
        base.class_eval do
          @_dependencies = Set.new
        end
      end

      # Adds a loaded dependency to a list so that it doesn't have
      # to be loaded again by another classes.
      #
      # @param [Dependency] dependency
      #
      def add_loaded(dependency)
        @_loaded_dependencies << dependency
      end

      # Returns whether the dependency is alread loaded or not.
      #
      # @param [Dependency] dependency
      # @return [Boolean]
      #
      def already_loaded?(dependency)
        @_loaded_dependencies.include?(dependency)
      end

      # Clears the list of loaded dependencies.
      #
      def clear_loaded
        @_loaded_dependencies.clear
      end
    end

    # Registers a new dependency.
    #
    # @param [String] name the name of the gem
    # @param [String] version the version of the gem
    #
    def dependency(name, version)
      @_dependencies << Dependency.new(name, version)
    end

    # Loads the registered dependencies.
    #
    # @raise DependencyManager::Error if the dependency can't be loaded.
    #
    def load_depenencies
      @_dependencies.each do |dependency|
        begin
          next if DependencyManager.already_loaded?(dependency)
          gem(dependency.name, dependency.version)
          require(dependency.name)
          DependencyManager.add_loaded(dependency)
          @_dependencies.delete(dependency)
        rescue Gem::LoadError
          args = [dependency.name, dependency.version]
          command = if running_under_bundler?
            BUNDLER_DECLARE_GEM % args
          else
            GEM_INSTALL_COMMAND % args.reverse
          end
          message = GEM_LOAD_MESSAGE % args

          raise Error.new(message + command)
        end
      end
    end

    # Returns whether all the dependencies has been loaded or not.
    #
    # @return [Boolean]
    #
    def dependencies_loaded?
      @_dependencies.empty?
    end

    private

    # Returns whether we are running under bundler or not
    #
    # @return [Boolean]
    #
    def running_under_bundler?
      !!(File.exists?('Gemfile') && ENV['BUNDLE_GEMFILE'])
    end
  end
end
Something went wrong with that request. Please try again.