Rich Codes

How Rubygems commands works

May 14, 2019 | 4 minute read

I’m currently working on GSoC, and I have to Integrate functionality from gem-web into RubyGems gem CLI. So, we’re going to add a new CLI option to rubygems. As my first task on it, my mentor Saroj Maharjan (@zoras) sent me some PRs (#1938 and #1944) to study, as well as the class Command of rubygems codebase. Here’s a bit of what I’ve learned:

Class Command

The first thing I noticed on the PRs was that every command inherits from the class Command. This class works like an interface to the children classes, that is, the children must override its methods to work properly. The comment at lib/rubygems/command.rb sums this up:

Base class for all Gem commands. When creating a new gem command, define #initialize, #execute, #arguments, #defaults_str, #description and #usage (as appropriate).

Overriding

So we need to override these methods to create our new command. Let’s take a look at them:

  • initialize: Initializes a new command, adding its name, a short description (that will be displayed in gem help commands). The argument defaults is a list of default arguments (that should be mirrored in defaults_str). Here’s the generic implementation of this method:
# Gem::Command's initialize method

def initialize(command, summary=nil, defaults={})
  @command = command
  @summary = summary
  @program_name = "gem #{command}"
  @defaults = defaults
  @options = defaults.dup
  @option_groups = Hash.new { |h,k| h[k] = [] }
  @deprecated_options = { command => {} }
  @parser = nil
  @when_invoked = nil
end

It’s possible to add new options to your command with add_option. The initilize method of our new command could look like this:

def initialize
  super 'new_method', 'Does an awesome thing.'

  add_option('--awesome', 'Adds awesomeness') do |value, options|
    options[:awesome] = value
  end
end


  • arguments: Describes the arguments that a command takes. It should return a left-justified string, one argument per line. Here’s the code from the command install, for example:
def arguments
  "GEMNAME       name of gem to install"
end


  • usage: Displays the usage for an individual gem command. The text “[options]” is automatically appended to the usage text. Take a look on the override of this method at Gem::Commands::UpdateCommand:
  def usage
    "#{program_name} GEMNAME [GEMNAME ...]"
  end


  • defaults_str: Defines the default arguments from the command. It’s really simple, take a look at this example:
# This method is similar to arguments, but displays the default values.
def defaults_str
  --document --no-force
end


  • description: This is just a long description for what the command does.
# You can add multiple lines as well
def description
  'The `signout` command is used to sign out from all current sessions,'\
  ' allowing you to sign in using a different set of credentials.'
end


  • execute: The generic method that you need to override to handle your command. Parsed option will fill options, while unparsed option will be left in options[:args]. An override could look something like this:
  # This will raise Gem::Exception if you don't override it
  def execute
    if options[:awesome]
      add_awesomeness
    end

    do_other_stuff
  end


Wrapping up

As you may have noticed, adding a new command to rubygems is not rocket science. Adding tests to your new code is very important too!

BTW, the tests from rubygems are written in Minitest, whereas gem-web’s are in Rspec, so I’ll need to convert them before integrating gem-web into rubygems. I’ll probably talk about this in next week’s post. Stay tuned! I’m out!

Categories

cli gsoc ruby rubygems