lib/bundler/dsl.rb



module Bundler
  class ManifestFileNotFound < StandardError; end
  class InvalidKey < StandardError; end
  class DefaultManifestNotFound < StandardError; end

  class Dsl
    def self.evaluate(file, bundle, environment)
      builder = new(bundle, environment)
      builder.instance_eval(File.read(file.to_s), file.to_s, 1)
      environment
    end

    def initialize(bundle, environment)
      @bundle = bundle
      @environment = environment
      @directory_sources = []
      @git_sources = {}
      @only, @except, @directory, @git = nil, nil, nil, nil
    end

    def bundle_path(path)
      @bundle.path = Pathname.new(path)
    end

    def bin_path(path)
      @bundle.bindir = Pathname.new(path)
    end

    def disable_rubygems
      @environment.rubygems = false
    end

    def disable_system_gems
      @environment.system_gems = false
    end

    def source(source)
      source = GemSource.new(@bundle, :uri => source)
      unless @environment.sources.include?(source)
        @environment.add_source(source)
      end
    end

    def only(*env)
      old, @only = @only, _combine_only(env)
      yield
      @only = old
    end

    def except(*env)
      old, @except = @except, _combine_except(env)
      yield
      @except = old
    end

    def directory(path, options = {})
      raise DirectorySourceError, "cannot nest calls to directory or git" if @directory || @git
      @directory = DirectorySource.new(@bundle, options.merge(:location => path))
      @directory_sources << @directory
      @environment.add_priority_source(@directory)
      retval = yield if block_given?
      @directory = nil
      retval
    end

    def git(uri, options = {})
      raise DirectorySourceError, "cannot nest calls to directory or git" if @directory || @git
      @git = GitSource.new(@bundle, options.merge(:uri => uri))
      @git_sources[uri] = @git
      @environment.add_priority_source(@git)
      retval = yield if block_given?
      @git = nil
      retval
    end

    def clear_sources
      @environment.clear_sources
    end

    def gem(name, *args)
      options = args.last.is_a?(Hash) ? args.pop : {}
      version = args.last

      keys = :vendored_at, :path, :only, :except, :git, :path, :bundle, :require_as, :tag, :branch, :ref
      unless (invalid = options.keys - keys).empty?
        raise InvalidKey, "Only #{keys.join(", ")} are valid options to #gem. You used #{invalid.join(", ")}"
      end

      if path = options.delete(:vendored_at)
        options[:path] = path
        warn "The :vendored_at option is deprecated. Use :path instead.\nFrom #{caller[0]}"
      end

      options[:only] = _combine_only(options[:only] || options["only"])
      options[:except] = _combine_except(options[:except] || options["except"])

      dep = Dependency.new(name, options.merge(:version => version))

      if options.key?(:bundle) && !options[:bundle]
        dep.source = SystemGemSource.new(@bundle)
      elsif @git || options[:git]
        dep.source = _handle_git_option(name, version, options)
      elsif @directory || options[:path]
        dep.source = _handle_vendored_option(name, version, options)
      end

      @environment.dependencies << dep
    end

  private

    def _version?(version)
      version && Gem::Version.new(version) rescue false
    end

    def _handle_vendored_option(name, version, options)
      dir, path = _find_directory_source(options[:path])

      if dir
        dir.required_specs << name
        dir.add_spec(path, name, version) if _version?(version)
        dir
      else
        directory options[:path] do
          _handle_vendored_option(name, version, {})
        end
      end
    end

    def _find_directory_source(path)
      if @directory
        return @directory, Pathname.new(path || '')
      end

      path = @bundle.gemfile.dirname.join(path)

      @directory_sources.each do |s|
        if s.location.expand_path.to_s < path.expand_path.to_s
          return s, path.relative_path_from(s.location)
        end
      end

      nil
    end

    def _handle_git_option(name, version, options)
      git    = options[:git].to_s
      ref    = options[:ref] || options[:tag]
      branch = options[:branch]

      if source = @git || @git_sources[git]
        if ref && source.ref != ref
          raise GitSourceError, "'#{git}' already specified with ref: #{source.ref}"
        elsif branch && source.branch != branch
          raise GitSourceError, "'#{git}' already specified with branch: #{source.branch}"
        end

        source.required_specs << name
        source.add_spec(Pathname.new(options[:path] || '.'), name, version) if _version?(version)
        source
      else
        git(git, :ref => ref, :branch => branch) do
          _handle_git_option(name, version, options)
        end
      end
    end

    def _combine_only(only)
      return @only unless only
      only = Array(only).compact.uniq.map { |o| o.to_s }
      only &= @only if @only
      only
    end

    def _combine_except(except)
      return @except unless except
      except = Array(except).compact.uniq.map { |o| o.to_s }
      except |= @except if @except
      except
    end
  end
end