123456789_123456789_123456789_123456789_123456789_

Class: Bundler::Injector

Relationships & Source Files
Inherits: Object
Defined in: lib/bundler/injector.rb

Constant Summary

Class Method Summary

Instance Method Summary

Constructor Details

.new(deps, options = {}) ⇒ Injector

[ GitHub ]

  
# File 'lib/bundler/injector.rb', line 17

def initialize(deps, options = {})
  @deps = deps
  @options = options
end

Class Method Details

.inject(new_deps, options = {})

[ GitHub ]

  
# File 'lib/bundler/injector.rb', line 7

def self.inject(new_deps, options = {})
  injector = new(new_deps, options)
  injector.inject(Bundler.default_gemfile, Bundler.default_lockfile)
end

.remove(gems, options = {})

[ GitHub ]

  
# File 'lib/bundler/injector.rb', line 12

def self.remove(gems, options = {})
  injector = new(gems, options)
  injector.remove(Bundler.default_gemfile, Bundler.default_lockfile)
end

Instance Method Details

#append_to(gemfile_path, new_gem_lines) (private)

[ GitHub ]

  
# File 'lib/bundler/injector.rb', line 127

def append_to(gemfile_path, new_gem_lines)
  gemfile_path.open("a") do |f|
    f.puts
    f.puts new_gem_lines
  end
end

#build_gem_lines(conservative_versioning) (private)

[ GitHub ]

  
# File 'lib/bundler/injector.rb', line 100

def build_gem_lines(conservative_versioning)
  @deps.map do |d|
    name = d.name.dump

    requirement = if conservative_versioning
      ", \"#{conservative_version(@definition.specs[d.name][0])}\""
    else
      ", #{d.requirement.as_list.map(&:dump).join(", ")}"
    end

    if d.groups != Array(:default)
      group = d.groups.size == 1 ? ", :group => #{d.groups.first.inspect}" : ", :groups => #{d.groups.inspect}"
    end

    source = ", :source => \"#{d.source}\"" unless d.source.nil?
    path = ", :path => \"#{d.path}\"" unless d.path.nil?
    git = ", :git => \"#{d.git}\"" unless d.git.nil?
    github = ", :github => \"#{d.github}\"" unless d.github.nil?
    branch = ", :branch => \"#{d.branch}\"" unless d.branch.nil?
    ref = ", :ref => \"#{d.ref}\"" unless d.ref.nil?
    glob = ", :glob => \"#{d.glob}\"" unless d.glob.nil?
    require_path = ", :require => #{convert_autorequire(d.autorequire)}" unless d.autorequire.nil?

    %(gem #{name}#{requirement}#{group}#{source}#{path}#{git}#{github}#{branch}#{ref}#{glob}#{require_path})
  end.join("\n")
end

#conservative_version(spec) (private)

[ GitHub ]

  
# File 'lib/bundler/injector.rb', line 80

def conservative_version(spec)
  version = spec.version
  return ">= 0" if version.nil?
  segments = version.segments
  seg_end_index = version >= Gem::Version.new("1.0") ? 1 : 2

  prerelease_suffix = version.to_s.delete_prefix(version.release.to_s) if version.prerelease?
  "#{version_prefix}#{segments[0..seg_end_index].join(".")}#{prerelease_suffix}"
end

#convert_autorequire(autorequire) (private)

[ GitHub ]

  
# File 'lib/bundler/injector.rb', line 279

def convert_autorequire(autorequire)
  autorequire = autorequire.first
  return autorequire if autorequire == "false"
  autorequire.inspect
end

#cross_check_for_errors(gemfile_path, original_deps, removed_deps, initial_gemfile) (private)

Parameters:

  • gemfile_path (Pathname)

    The Gemfile from which to remove dependencies.

  • original_deps (Array)

    Array of original dependencies.

  • removed_deps (Array)

    Array of removed dependencies.

  • initial_gemfile (Array)

    Contents of original Gemfile before any operation.

[ GitHub ]

  
# File 'lib/bundler/injector.rb', line 250

def cross_check_for_errors(gemfile_path, original_deps, removed_deps, initial_gemfile)
  # evaluate the new gemfile to look for any failure cases
  builder = Dsl.new
  builder.eval_gemfile(gemfile_path)

  # record gems which were removed but not requested
  extra_removed_gems = original_deps - builder.dependencies

  # if some extra gems were removed then raise error
  # and revert Gemfile to original
  unless extra_removed_gems.empty?
    SharedHelpers.write_to_gemfile(gemfile_path, initial_gemfile.join)

    raise InvalidOption, "Gems could not be removed. #{extra_removed_gems.join(", ")} would also have been removed. Bundler cannot continue."
  end

  # record gems which could not be removed due to some reasons
  errored_deps = builder.dependencies.select {|d| d.gemfile == gemfile_path } & removed_deps.select {|d| d.gemfile == gemfile_path }

  show_warning "#{errored_deps.map(&:name).join(", ")} could not be removed." unless errored_deps.empty?

  # return actual removed dependencies
  removed_deps - errored_deps
end

#inject(gemfile_path, lockfile_path) ⇒ Array

Parameters:

  • gemfile_path (Pathname)

    The Gemfile in which to inject the new dependency.

  • lockfile_path (Pathname)

    The lockfile in which to inject the new dependency.

[ GitHub ]

  
# File 'lib/bundler/injector.rb', line 25

def inject(gemfile_path, lockfile_path)
  Bundler.definition.ensure_equivalent_gemfile_and_lockfile(true)

  # temporarily unfreeze
  Bundler.settings.temporary(deployment: false, frozen: false) do
    # evaluate the Gemfile we have now
    builder = Dsl.new
    builder.eval_gemfile(gemfile_path)

    # don't inject any gems that are already in the Gemfile
    @deps -= builder.dependencies

    # add new deps to the end of the in-memory Gemfile
    # Set conservative versioning to false because
    # we want to let the resolver resolve the version first
    builder.eval_gemfile(INJECTED_GEMS, build_gem_lines(false)) if @deps.any?

    # resolve to see if the new deps broke anything
    @definition = builder.to_definition(lockfile_path, {})
    @definition.resolve_remotely!

    # since nothing broke, we can add those gems to the gemfile
    append_to(gemfile_path, build_gem_lines(@options[:conservative_versioning])) if @deps.any?

    # since we resolved successfully, write out the lockfile
    @definition.lock

    # invalidate the cached Bundler.definition
    Bundler.reset_paths!

    # return an array of the deps that we added
    @deps
  end
end

#is_not_within_comment?(line, match_data) ⇒ Boolean (private)

Parameters:

  • line (String)

    Individual line of gemfile content.

  • match_data (MatchData)

    Data about Regex match.

[ GitHub ]

  
# File 'lib/bundler/injector.rb', line 218

def is_not_within_comment?(line, match_data)
  match_start_index = match_data.offset(0).first
  !line[0..match_start_index].include?("#")
end

#remove(gemfile_path, lockfile_path) ⇒ Array

Parameters:

  • gemfile_path (Pathname)

    The Gemfile from which to remove dependencies.

  • lockfile_path (Pathname)

    The lockfile from which to remove dependencies.

[ GitHub ]

  
# File 'lib/bundler/injector.rb', line 63

def remove(gemfile_path, lockfile_path)
  # remove gems from each gemfiles we have
  Bundler.definition.gemfiles.each do |path|
    deps = remove_deps(path)

    show_warning("No gems were removed from the gemfile.") if deps.empty?

    deps.each {|dep| Bundler.ui.confirm "#{SharedHelpers.pretty_dependency(dep)} was removed." }
  end

  # Invalidate the cached Bundler.definition.
  # This prevents e.g. `bundle remove ...` from using outdated information.
  Bundler.reset_paths!
end

#remove_deps(gemfile_path) (private)

evaluates a gemfile to remove the specified gem from it.

[ GitHub ]

  
# File 'lib/bundler/injector.rb', line 136

def remove_deps(gemfile_path)
  initial_gemfile = File.readlines(gemfile_path)

  Bundler.ui.info "Removing gems from #{gemfile_path}"

  # evaluate the Gemfile we have
  builder = Dsl.new
  builder.eval_gemfile(gemfile_path)

  removed_deps = remove_gems_from_dependencies(builder, @deps, gemfile_path)

  # abort the operation if no gems were removed
  # no need to operate on gemfile further
  return [] if removed_deps.empty?

  cleaned_gemfile = remove_gems_from_gemfile(@deps, gemfile_path)

  SharedHelpers.write_to_gemfile(gemfile_path, cleaned_gemfile)

  # check for errors
  # including extra gems being removed
  # or some gems not being removed
  # and return the actual removed deps
  cross_check_for_errors(gemfile_path, builder.dependencies, removed_deps, initial_gemfile)
end

#remove_gems_from_dependencies(builder, gems, gemfile_path) ⇒ Array (private)

Parameters:

  • builder (Dsl)

    Dsl object of current Gemfile.

  • gems (Array)

    Array of names of gems to be removed.

  • gemfile_path (Pathname)

    Path of the Gemfile.

Returns:

  • (Array)

    Array of removed dependencies.

[ GitHub ]

  
# File 'lib/bundler/injector.rb', line 166

def remove_gems_from_dependencies(builder, gems, gemfile_path)
  removed_deps = []

  gems.each do |gem_name|
    deleted_dep = builder.dependencies.find {|d| d.name == gem_name }

    if deleted_dep.nil?
      raise GemfileError, "`#{gem_name}` is not specified in #{gemfile_path} so it could not be removed."
    end

    builder.dependencies.delete(deleted_dep)

    removed_deps << deleted_dep
  end

  removed_deps
end

#remove_gems_from_gemfile(gems, gemfile_path) (private)

Parameters:

  • gems (Array)

    Array of names of gems to be removed.

  • gemfile_path (Pathname)

    The Gemfile from which to remove dependencies.

[ GitHub ]

  
# File 'lib/bundler/injector.rb', line 186

def remove_gems_from_gemfile(gems, gemfile_path)
  patterns = /gem\s+(['"])#{Regexp.union(gems)}\1|gem\s*\((['"])#{Regexp.union(gems)}\2.*\)/
  new_gemfile = []
  multiline_removal = false
  File.readlines(gemfile_path).each do |line|
    match_data = line.match(patterns)
    if match_data && is_not_within_comment?(line, match_data)
      multiline_removal = line.rstrip.end_with?(",")
      # skip lines which match the regex
      next
    end

    # skip followup lines until line does not end with ','
    new_gemfile << line unless multiline_removal
    multiline_removal = line.rstrip.end_with?(",") if multiline_removal
  end

  # remove line \n and append them with other strings
  new_gemfile.each_with_index do |_line, index|
    if new_gemfile[index + 1] == "\n"
      new_gemfile[index] += new_gemfile[index + 1]
      new_gemfile.delete_at(index + 1)
    end
  end

  %w[group source env install_if].each {|block| remove_nested_blocks(new_gemfile, block) }

  new_gemfile.join.chomp
end

#remove_nested_blocks(gemfile, block_name) (private)

Parameters:

  • gemfile (Array)

    Array of gemfile contents.

  • block_name (String)

    Name of block name to look for.

[ GitHub ]

  
# File 'lib/bundler/injector.rb', line 225

def remove_nested_blocks(gemfile, block_name)
  nested_blocks = 0

  # count number of nested blocks
  gemfile.each_with_index {|line, index| nested_blocks += 1 if !gemfile[index + 1].nil? && gemfile[index + 1].include?(block_name) && line.include?(block_name) }

  while nested_blocks >= 0
    nested_blocks -= 1

    gemfile.each_with_index do |line, index|
      next unless !line.nil? && line.strip.start_with?(block_name)
      if /^\s*end\s*$/.match?(gemfile[index + 1])
        gemfile[index] = nil
        gemfile[index + 1] = nil
      end
    end

    gemfile.compact!
  end
end

#show_warning(message) (private)

[ GitHub ]

  
# File 'lib/bundler/injector.rb', line 275

def show_warning(message)
  Bundler.ui.info Bundler.ui.add_color(message, :yellow)
end

#version_prefix (private)

[ GitHub ]

  
# File 'lib/bundler/injector.rb', line 90

def version_prefix
  if @options[:strict]
    "= "
  elsif @options[:optimistic]
    ">= "
  else
    "~> "
  end
end