summaryrefslogtreecommitdiff
path: root/lib/bundler/cli/common.rb
blob: 7ef6deb2cf0b8a508f6e48386abebc612828c9fb (plain)
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
127
128
129
130
131
132
133
134
135
136
137
138
# frozen_string_literal: true

module Bundler
  module CLI::Common
    def self.output_post_install_messages(messages)
      return if Bundler.settings["ignore_messages"]
      messages.to_a.each do |name, msg|
        print_post_install_message(name, msg) unless Bundler.settings["ignore_messages.#{name}"]
      end
    end

    def self.print_post_install_message(name, msg)
      Bundler.ui.confirm "Post-install message from #{name}:"
      Bundler.ui.info msg
    end

    def self.output_fund_metadata_summary
      return if Bundler.settings["ignore_funding_requests"]
      definition = Bundler.definition
      current_dependencies = definition.requested_dependencies
      current_specs = definition.specs

      count = current_dependencies.count {|dep| current_specs[dep.name].first.metadata.key?("funding_uri") }

      return if count.zero?

      intro = count > 1 ? "#{count} installed gems you directly depend on are" : "#{count} installed gem you directly depend on is"
      message = "#{intro} looking for funding.\n  Run `bundle fund` for details"
      Bundler.ui.info message
    end

    def self.output_without_groups_message(command)
      return if Bundler.settings[:without].empty?
      Bundler.ui.confirm without_groups_message(command)
    end

    def self.without_groups_message(command)
      command_in_past_tense = command == :install ? "installed" : "updated"
      groups = Bundler.settings[:without]
      "Gems in the #{verbalize_groups(groups)} were not #{command_in_past_tense}."
    end

    def self.verbalize_groups(groups)
      groups.map! {|g| "'#{g}'" }
      group_list = [groups[0...-1].join(", "), groups[-1..-1]].
        reject {|s| s.to_s.empty? }.join(" and ")
      group_str = groups.size == 1 ? "group" : "groups"
      "#{group_str} #{group_list}"
    end

    def self.select_spec(name, regex_match = nil)
      specs = []
      regexp = Regexp.new(name) if regex_match

      Bundler.definition.specs.each do |spec|
        return spec if spec.name == name
        specs << spec if regexp && spec.name.match?(regexp)
      end

      default_spec = default_gem_spec(name)
      specs << default_spec if default_spec

      case specs.count
      when 0
        dep_in_other_group = Bundler.definition.current_dependencies.find {|dep|dep.name == name }

        if dep_in_other_group
          raise GemNotFound, "Could not find gem '#{name}', because it's in the #{verbalize_groups(dep_in_other_group.groups)}, configured to be ignored."
        else
          raise GemNotFound, gem_not_found_message(name, Bundler.definition.dependencies)
        end
      when 1
        specs.first
      else
        ask_for_spec_from(specs)
      end
    rescue RegexpError
      raise GemNotFound, gem_not_found_message(name, Bundler.definition.dependencies)
    end

    def self.default_gem_spec(name)
      gem_spec = Gem::Specification.find_all_by_name(name).last
      gem_spec if gem_spec&.default_gem?
    end

    def self.ask_for_spec_from(specs)
      specs.each_with_index do |spec, index|
        Bundler.ui.info "#{index.succ} : #{spec.name}", true
      end
      Bundler.ui.info "0 : - exit -", true

      num = Bundler.ui.ask("> ").to_i
      num > 0 ? specs[num - 1] : nil
    end

    def self.gem_not_found_message(missing_gem_name, alternatives)
      require_relative "../similarity_detector"
      message = "Could not find gem '#{missing_gem_name}'."
      alternate_names = alternatives.map {|a| a.respond_to?(:name) ? a.name : a }
      suggestions = SimilarityDetector.new(alternate_names).similar_word_list(missing_gem_name)
      message += "\nDid you mean #{suggestions}?" if suggestions
      message
    end

    def self.ensure_all_gems_in_lockfile!(names, locked_gems = Bundler.locked_gems)
      return unless locked_gems

      locked_names = locked_gems.specs.map(&:name).uniq
      names.-(locked_names).each do |g|
        raise GemNotFound, gem_not_found_message(g, locked_names)
      end
    end

    def self.configure_gem_version_promoter(definition, options)
      patch_level = patch_level_options(options)
      patch_level << :patch if patch_level.empty? && Bundler.settings[:prefer_patch]
      raise InvalidOption, "Provide only one of the following options: #{patch_level.join(", ")}" unless patch_level.length <= 1

      definition.gem_version_promoter.tap do |gvp|
        gvp.level = patch_level.first || :major
        gvp.strict = options[:strict] || options["filter-strict"]
        gvp.pre = options[:pre]
      end
    end

    def self.patch_level_options(options)
      [:major, :minor, :patch].select {|v| options.keys.include?(v.to_s) }
    end

    def self.clean_after_install?
      clean = Bundler.settings[:clean]
      return clean unless clean.nil?
      clean ||= Bundler.feature_flag.auto_clean_without_path? && Bundler.settings[:path].nil?
      clean &&= !Bundler.use_system_gems?
      clean
    end
  end
end