1# frozen_string_literal: true 2#-- 3# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. 4# All rights reserved. 5# See LICENSE.txt for permissions. 6#++ 7 8require 'optparse' 9require 'rubygems/requirement' 10require 'rubygems/user_interaction' 11 12## 13# Base class for all Gem commands. When creating a new gem command, define 14# #initialize, #execute, #arguments, #defaults_str, #description and #usage 15# (as appropriate). See the above mentioned methods for details. 16# 17# A very good example to look at is Gem::Commands::ContentsCommand 18 19class Gem::Command 20 21 include Gem::UserInteraction 22 23 ## 24 # The name of the command. 25 26 attr_reader :command 27 28 ## 29 # The options for the command. 30 31 attr_reader :options 32 33 ## 34 # The default options for the command. 35 36 attr_accessor :defaults 37 38 ## 39 # The name of the command for command-line invocation. 40 41 attr_accessor :program_name 42 43 ## 44 # A short description of the command. 45 46 attr_accessor :summary 47 48 ## 49 # Arguments used when building gems 50 51 def self.build_args 52 @build_args ||= [] 53 end 54 55 def self.build_args=(value) 56 @build_args = value 57 end 58 59 def self.common_options 60 @common_options ||= [] 61 end 62 63 def self.add_common_option(*args, &handler) 64 Gem::Command.common_options << [args, handler] 65 end 66 67 def self.extra_args 68 @extra_args ||= [] 69 end 70 71 def self.extra_args=(value) 72 case value 73 when Array 74 @extra_args = value 75 when String 76 @extra_args = value.split 77 end 78 end 79 80 ## 81 # Return an array of extra arguments for the command. The extra arguments 82 # come from the gem configuration file read at program startup. 83 84 def self.specific_extra_args(cmd) 85 specific_extra_args_hash[cmd] 86 end 87 88 ## 89 # Add a list of extra arguments for the given command. +args+ may be an 90 # array or a string to be split on white space. 91 92 def self.add_specific_extra_args(cmd,args) 93 args = args.split(/\s+/) if args.kind_of? String 94 specific_extra_args_hash[cmd] = args 95 end 96 97 ## 98 # Accessor for the specific extra args hash (self initializing). 99 100 def self.specific_extra_args_hash 101 @specific_extra_args_hash ||= Hash.new do |h,k| 102 h[k] = Array.new 103 end 104 end 105 106 ## 107 # Initializes a generic gem command named +command+. +summary+ is a short 108 # description displayed in `gem help commands`. +defaults+ are the default 109 # options. Defaults should be mirrored in #defaults_str, unless there are 110 # none. 111 # 112 # When defining a new command subclass, use add_option to add command-line 113 # switches. 114 # 115 # Unhandled arguments (gem names, files, etc.) are left in 116 # <tt>options[:args]</tt>. 117 118 def initialize(command, summary=nil, defaults={}) 119 @command = command 120 @summary = summary 121 @program_name = "gem #{command}" 122 @defaults = defaults 123 @options = defaults.dup 124 @option_groups = Hash.new { |h,k| h[k] = [] } 125 @parser = nil 126 @when_invoked = nil 127 end 128 129 ## 130 # True if +long+ begins with the characters from +short+. 131 132 def begins?(long, short) 133 return false if short.nil? 134 long[0, short.length] == short 135 end 136 137 ## 138 # Override to provide command handling. 139 # 140 # #options will be filled in with your parsed options, unparsed options will 141 # be left in <tt>options[:args]</tt>. 142 # 143 # See also: #get_all_gem_names, #get_one_gem_name, 144 # #get_one_optional_argument 145 146 def execute 147 raise Gem::Exception, "generic command has no actions" 148 end 149 150 ## 151 # Display to the user that a gem couldn't be found and reasons why 152 #-- 153 # TODO: replace +domain+ with a parameter to suppress suggestions 154 155 def show_lookup_failure(gem_name, version, errors, domain, required_by = nil) 156 gem = "'#{gem_name}' (#{version})" 157 msg = String.new "Could not find a valid gem #{gem}" 158 159 if errors and !errors.empty? 160 msg << ", here is why:\n" 161 errors.each { |x| msg << " #{x.wordy}\n" } 162 else 163 if required_by and gem != required_by 164 msg << " (required by #{required_by}) in any repository" 165 else 166 msg << " in any repository" 167 end 168 end 169 170 alert_error msg 171 172 unless domain == :local # HACK 173 suggestions = Gem::SpecFetcher.fetcher.suggest_gems_from_name gem_name 174 175 unless suggestions.empty? 176 alert_error "Possible alternatives: #{suggestions.join(", ")}" 177 end 178 end 179 end 180 181 ## 182 # Get all gem names from the command line. 183 184 def get_all_gem_names 185 args = options[:args] 186 187 if args.nil? or args.empty? 188 raise Gem::CommandLineError, 189 "Please specify at least one gem name (e.g. gem build GEMNAME)" 190 end 191 192 args.select { |arg| arg !~ /^-/ } 193 end 194 195 ## 196 # Get all [gem, version] from the command line. 197 # 198 # An argument in the form gem:ver is pull apart into the gen name and version, 199 # respectively. 200 def get_all_gem_names_and_versions 201 get_all_gem_names.map do |name| 202 if /\A(.*):(#{Gem::Requirement::PATTERN_RAW})\z/ =~ name 203 [$1, $2] 204 else 205 [name] 206 end 207 end 208 end 209 210 ## 211 # Get a single gem name from the command line. Fail if there is no gem name 212 # or if there is more than one gem name given. 213 214 def get_one_gem_name 215 args = options[:args] 216 217 if args.nil? or args.empty? 218 raise Gem::CommandLineError, 219 "Please specify a gem name on the command line (e.g. gem build GEMNAME)" 220 end 221 222 if args.size > 1 223 raise Gem::CommandLineError, 224 "Too many gem names (#{args.join(', ')}); please specify only one" 225 end 226 227 args.first 228 end 229 230 ## 231 # Get a single optional argument from the command line. If more than one 232 # argument is given, return only the first. Return nil if none are given. 233 234 def get_one_optional_argument 235 args = options[:args] || [] 236 args.first 237 end 238 239 ## 240 # Override to provide details of the arguments a command takes. It should 241 # return a left-justified string, one argument per line. 242 # 243 # For example: 244 # 245 # def usage 246 # "#{program_name} FILE [FILE ...]" 247 # end 248 # 249 # def arguments 250 # "FILE name of file to find" 251 # end 252 253 def arguments 254 "" 255 end 256 257 ## 258 # Override to display the default values of the command options. (similar to 259 # +arguments+, but displays the default values). 260 # 261 # For example: 262 # 263 # def defaults_str 264 # --no-gems-first --no-all 265 # end 266 267 def defaults_str 268 "" 269 end 270 271 ## 272 # Override to display a longer description of what this command does. 273 274 def description 275 nil 276 end 277 278 ## 279 # Override to display the usage for an individual gem command. 280 # 281 # The text "[options]" is automatically appended to the usage text. 282 283 def usage 284 program_name 285 end 286 287 ## 288 # Display the help message for the command. 289 290 def show_help 291 parser.program_name = usage 292 say parser 293 end 294 295 ## 296 # Invoke the command with the given list of arguments. 297 298 def invoke(*args) 299 invoke_with_build_args args, nil 300 end 301 302 ## 303 # Invoke the command with the given list of normal arguments 304 # and additional build arguments. 305 306 def invoke_with_build_args(args, build_args) 307 handle_options args 308 309 options[:build_args] = build_args 310 311 if options[:silent] 312 old_ui = self.ui 313 self.ui = ui = Gem::SilentUI.new 314 end 315 316 if options[:help] 317 show_help 318 elsif @when_invoked 319 @when_invoked.call options 320 else 321 execute 322 end 323 ensure 324 if ui 325 self.ui = old_ui 326 ui.close 327 end 328 end 329 330 ## 331 # Call the given block when invoked. 332 # 333 # Normal command invocations just executes the +execute+ method of the 334 # command. Specifying an invocation block allows the test methods to 335 # override the normal action of a command to determine that it has been 336 # invoked correctly. 337 338 def when_invoked(&block) 339 @when_invoked = block 340 end 341 342 ## 343 # Add a command-line option and handler to the command. 344 # 345 # See OptionParser#make_switch for an explanation of +opts+. 346 # 347 # +handler+ will be called with two values, the value of the argument and 348 # the options hash. 349 # 350 # If the first argument of add_option is a Symbol, it's used to group 351 # options in output. See `gem help list` for an example. 352 353 def add_option(*opts, &handler) # :yields: value, options 354 group_name = Symbol === opts.first ? opts.shift : :options 355 356 @option_groups[group_name] << [opts, handler] 357 end 358 359 ## 360 # Remove previously defined command-line argument +name+. 361 362 def remove_option(name) 363 @option_groups.each do |_, option_list| 364 option_list.reject! { |args, _| args.any? { |x| x.is_a?(String) && x =~ /^#{name}/ } } 365 end 366 end 367 368 ## 369 # Merge a set of command options with the set of default options (without 370 # modifying the default option hash). 371 372 def merge_options(new_options) 373 @options = @defaults.clone 374 new_options.each do |k,v| @options[k] = v end 375 end 376 377 ## 378 # True if the command handles the given argument list. 379 380 def handles?(args) 381 begin 382 parser.parse!(args.dup) 383 return true 384 rescue 385 return false 386 end 387 end 388 389 ## 390 # Handle the given list of arguments by parsing them and recording the 391 # results. 392 393 def handle_options(args) 394 args = add_extra_args(args) 395 @options = Marshal.load Marshal.dump @defaults # deep copy 396 parser.parse!(args) 397 @options[:args] = args 398 end 399 400 ## 401 # Adds extra args from ~/.gemrc 402 403 def add_extra_args(args) 404 result = [] 405 406 s_extra = Gem::Command.specific_extra_args(@command) 407 extra = Gem::Command.extra_args + s_extra 408 409 until extra.empty? do 410 ex = [] 411 ex << extra.shift 412 ex << extra.shift if extra.first.to_s =~ /^[^-]/ 413 result << ex if handles?(ex) 414 end 415 416 result.flatten! 417 result.concat(args) 418 result 419 end 420 421 private 422 423 def add_parser_description # :nodoc: 424 return unless description 425 426 formatted = description.split("\n\n").map do |chunk| 427 wrap chunk, 80 - 4 428 end.join "\n" 429 430 @parser.separator nil 431 @parser.separator " Description:" 432 formatted.split("\n").each do |line| 433 @parser.separator " #{line.rstrip}" 434 end 435 end 436 437 def add_parser_options # :nodoc: 438 @parser.separator nil 439 440 regular_options = @option_groups.delete :options 441 442 configure_options "", regular_options 443 444 @option_groups.sort_by { |n,_| n.to_s }.each do |group_name, option_list| 445 @parser.separator nil 446 configure_options group_name, option_list 447 end 448 end 449 450 ## 451 # Adds a section with +title+ and +content+ to the parser help view. Used 452 # for adding command arguments and default arguments. 453 454 def add_parser_run_info(title, content) 455 return if content.empty? 456 457 @parser.separator nil 458 @parser.separator " #{title}:" 459 content.split(/\n/).each do |line| 460 @parser.separator " #{line}" 461 end 462 end 463 464 def add_parser_summary # :nodoc: 465 return unless @summary 466 467 @parser.separator nil 468 @parser.separator " Summary:" 469 wrap(@summary, 80 - 4).split("\n").each do |line| 470 @parser.separator " #{line.strip}" 471 end 472 end 473 474 ## 475 # Create on demand parser. 476 477 def parser 478 create_option_parser if @parser.nil? 479 @parser 480 end 481 482 ## 483 # Creates an option parser and fills it in with the help info for the 484 # command. 485 486 def create_option_parser 487 @parser = OptionParser.new 488 489 add_parser_options 490 491 @parser.separator nil 492 configure_options "Common", Gem::Command.common_options 493 494 add_parser_run_info "Arguments", arguments 495 add_parser_summary 496 add_parser_description 497 add_parser_run_info "Defaults", defaults_str 498 end 499 500 def configure_options(header, option_list) 501 return if option_list.nil? or option_list.empty? 502 503 header = header.to_s.empty? ? '' : "#{header} " 504 @parser.separator " #{header}Options:" 505 506 option_list.each do |args, handler| 507 @parser.on(*args) do |value| 508 handler.call(value, @options) 509 end 510 end 511 512 @parser.separator '' 513 end 514 515 ## 516 # Wraps +text+ to +width+ 517 518 def wrap(text, width) # :doc: 519 text.gsub(/(.{1,#{width}})( +|$\n?)|(.{1,#{width}})/, "\\1\\3\n") 520 end 521 522 # ---------------------------------------------------------------- 523 # Add the options common to all commands. 524 525 add_common_option('-h', '--help', 526 'Get help on this command') do |value, options| 527 options[:help] = true 528 end 529 530 add_common_option('-V', '--[no-]verbose', 531 'Set the verbose level of output') do |value, options| 532 # Set us to "really verbose" so the progress meter works 533 if Gem.configuration.verbose and value 534 Gem.configuration.verbose = 1 535 else 536 Gem.configuration.verbose = value 537 end 538 end 539 540 add_common_option('-q', '--quiet', 'Silence command progress meter') do |value, options| 541 Gem.configuration.verbose = false 542 end 543 544 add_common_option("--silent", 545 "Silence RubyGems output") do |value, options| 546 options[:silent] = true 547 end 548 549 # Backtrace and config-file are added so they show up in the help 550 # commands. Both options are actually handled before the other 551 # options get parsed. 552 553 add_common_option('--config-file FILE', 554 'Use this config file instead of default') do 555 end 556 557 add_common_option('--backtrace', 558 'Show stack backtrace on errors') do 559 end 560 561 add_common_option('--debug', 562 'Turn on Ruby debugging') do 563 end 564 565 add_common_option('--norc', 566 'Avoid loading any .gemrc file') do 567 end 568 569 570 # :stopdoc: 571 572 HELP = <<-HELP.freeze 573RubyGems is a sophisticated package manager for Ruby. This is a 574basic help message containing pointers to more information. 575 576 Usage: 577 gem -h/--help 578 gem -v/--version 579 gem command [arguments...] [options...] 580 581 Examples: 582 gem install rake 583 gem list --local 584 gem build package.gemspec 585 gem help install 586 587 Further help: 588 gem help commands list all 'gem' commands 589 gem help examples show some examples of usage 590 gem help gem_dependencies gem dependencies file guide 591 gem help platforms gem platforms guide 592 gem help <COMMAND> show help on COMMAND 593 (e.g. 'gem help install') 594 gem server present a web page at 595 http://localhost:8808/ 596 with info about installed gems 597 Further information: 598 http://guides.rubygems.org 599 HELP 600 601 # :startdoc: 602 603end 604 605## 606# \Commands will be placed in this namespace 607 608module Gem::Commands 609end 610