1# frozen_string_literal: true 2require 'rubygems/command' 3require 'rubygems/local_remote_options' 4require 'rubygems/version_option' 5 6class Gem::Commands::DependencyCommand < Gem::Command 7 8 include Gem::LocalRemoteOptions 9 include Gem::VersionOption 10 11 def initialize 12 super 'dependency', 13 'Show the dependencies of an installed gem', 14 :version => Gem::Requirement.default, :domain => :local 15 16 add_version_option 17 add_platform_option 18 add_prerelease_option 19 20 add_option('-R', '--[no-]reverse-dependencies', 21 'Include reverse dependencies in the output') do 22 |value, options| 23 options[:reverse_dependencies] = value 24 end 25 26 add_option('-p', '--pipe', 27 "Pipe Format (name --version ver)") do |value, options| 28 options[:pipe_format] = value 29 end 30 31 add_local_remote_options 32 end 33 34 def arguments # :nodoc: 35 "REGEXP show dependencies for gems whose names start with REGEXP" 36 end 37 38 def defaults_str # :nodoc: 39 "--local --version '#{Gem::Requirement.default}' --no-reverse-dependencies" 40 end 41 42 def description # :nodoc: 43 <<-EOF 44The dependency commands lists which other gems a given gem depends on. For 45local gems only the reverse dependencies can be shown (which gems depend on 46the named gem). 47 48The dependency list can be displayed in a format suitable for piping for 49use with other commands. 50 EOF 51 end 52 53 def usage # :nodoc: 54 "#{program_name} REGEXP" 55 end 56 57 def fetch_remote_specs(dependency) # :nodoc: 58 fetcher = Gem::SpecFetcher.fetcher 59 60 ss, = fetcher.spec_for_dependency dependency 61 62 ss.map { |spec, _| spec } 63 end 64 65 def fetch_specs(name_pattern, dependency) # :nodoc: 66 specs = [] 67 68 if local? 69 specs.concat Gem::Specification.stubs.find_all { |spec| 70 name_pattern =~ spec.name and 71 dependency.requirement.satisfied_by? spec.version 72 }.map(&:to_spec) 73 end 74 75 specs.concat fetch_remote_specs dependency if remote? 76 77 ensure_specs specs 78 79 specs.uniq.sort 80 end 81 82 def gem_dependency(pattern, version, prerelease) # :nodoc: 83 dependency = Gem::Deprecate.skip_during { 84 Gem::Dependency.new pattern, version 85 } 86 87 dependency.prerelease = prerelease 88 89 dependency 90 end 91 92 def display_pipe(specs) # :nodoc: 93 specs.each do |spec| 94 unless spec.dependencies.empty? 95 spec.dependencies.sort_by { |dep| dep.name }.each do |dep| 96 say "#{dep.name} --version '#{dep.requirement}'" 97 end 98 end 99 end 100 end 101 102 def display_readable(specs, reverse) # :nodoc: 103 response = String.new 104 105 specs.each do |spec| 106 response << print_dependencies(spec) 107 unless reverse[spec.full_name].empty? 108 response << " Used by\n" 109 reverse[spec.full_name].each do |sp, dep| 110 response << " #{sp} (#{dep})\n" 111 end 112 end 113 response << "\n" 114 end 115 116 say response 117 end 118 119 def execute 120 ensure_local_only_reverse_dependencies 121 122 pattern = name_pattern options[:args] 123 124 dependency = 125 gem_dependency pattern, options[:version], options[:prerelease] 126 127 specs = fetch_specs pattern, dependency 128 129 reverse = reverse_dependencies specs 130 131 if options[:pipe_format] 132 display_pipe specs 133 else 134 display_readable specs, reverse 135 end 136 end 137 138 def ensure_local_only_reverse_dependencies # :nodoc: 139 if options[:reverse_dependencies] and remote? and not local? 140 alert_error 'Only reverse dependencies for local gems are supported.' 141 terminate_interaction 1 142 end 143 end 144 145 def ensure_specs(specs) # :nodoc: 146 return unless specs.empty? 147 148 patterns = options[:args].join ',' 149 say "No gems found matching #{patterns} (#{options[:version]})" if 150 Gem.configuration.verbose 151 152 terminate_interaction 1 153 end 154 155 def print_dependencies(spec, level = 0) # :nodoc: 156 response = String.new 157 response << ' ' * level + "Gem #{spec.full_name}\n" 158 unless spec.dependencies.empty? 159 spec.dependencies.sort_by { |dep| dep.name }.each do |dep| 160 response << ' ' * level + " #{dep}\n" 161 end 162 end 163 response 164 end 165 166 def remote_specs(dependency) # :nodoc: 167 fetcher = Gem::SpecFetcher.fetcher 168 169 ss, _ = fetcher.spec_for_dependency dependency 170 171 ss.map { |s,o| s } 172 end 173 174 def reverse_dependencies(specs) # :nodoc: 175 reverse = Hash.new { |h, k| h[k] = [] } 176 177 return reverse unless options[:reverse_dependencies] 178 179 specs.each do |spec| 180 reverse[spec.full_name] = find_reverse_dependencies spec 181 end 182 183 reverse 184 end 185 186 ## 187 # Returns an Array of [specification, dep] that are satisfied by +spec+. 188 189 def find_reverse_dependencies(spec) # :nodoc: 190 result = [] 191 192 Gem::Specification.each do |sp| 193 sp.dependencies.each do |dep| 194 dep = Gem::Dependency.new(*dep) unless Gem::Dependency === dep 195 196 if spec.name == dep.name and 197 dep.requirement.satisfied_by?(spec.version) 198 result << [sp.full_name, dep] 199 end 200 end 201 end 202 203 result 204 end 205 206 private 207 208 def name_pattern(args) 209 args << '' if args.empty? 210 211 if args.length == 1 and args.first =~ /\A\/(.*)\/(i)?\z/m 212 flags = $2 ? Regexp::IGNORECASE : nil 213 Regexp.new $1, flags 214 else 215 /\A#{Regexp.union(*args)}/ 216 end 217 end 218end 219