Creating a RubyGems plugin
Do you know when you install a gem
and it adds a custom command to RubyGems
? Then you can just run gem <custom-command> <params>
and it does something cool?
Well, that is just a RubyGems
plugin, and although it’s not very well documented, it’s not that hard to create one.
Our goal
Our goal here is just to understand what are the pieces that we need to put together to create one of these plugins. We are not going to create something that is amazingly useful.
Here’s what it will do: It will add a repo
command, that will just open a GitHub repository in your browser.
$ gem repo ruby/ruby
# should open http://github.com/ruby/ruby in your browser
So let’s get our hands dirty.
It’s just a gem
A RubyGems
plugin is just a normal gem
, with some specific characteristics. To create a gem
you can use any template or generator you like. I’ll use bundle gem repo
to create the skeleton for our plugin.
$ bundle gem repo
├── Gemfile
├── LICENSE.txt
├── README.md
├── Rakefile
├── bin
│ ├── console
│ └── setup
├── lib
│ ├── repo
│ │ └── version.rb
│ └── repo.rb
└── repo.gemspec
And the first step is to update the repo.gemspec
file with your plugin details. It could look something like this:
# coding: utf-8
lib = File.expand_path('../lib', __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require 'repo/version'
Gem::Specification.new do |spec|
spec.name = "repo"
spec.version = Repo::VERSION
spec.authors = ["Your name"]
spec.email = ["your@email.com"]
spec.summary = %q{Opens github repo}
spec.description = %q{Opens github repo}
spec.license = "MIT"
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
spec.bindir = "exe"
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
spec.require_paths = ["lib"]
spec.add_development_dependency "bundler", "~> 1.8"
spec.add_development_dependency "rake", "~> 10.0"
end
After you run git add .
, you should be able to build your gem with gem build repo.gemspec
and check that a file called repo-0.1.0.gem
was created. We are good to go!
The RubyGems requirements
RubyGems
will look for a file called rubygems_plugin.rb
in the root of the require_path
that was defined in the gemspec. In our case, it’s in the lib
directory, so we will create this file there:
# lib/rubygems_plugin.rb
require "rubygems/command_manager"
Gem::CommandManager.instance.register_command(:repo)
Here we are just registering a new command, so RubyGems
will be able to find it when someone tries to execute our gem repo
.
That’s the same way the builtin commands are registered, as you can see here.
After our custom command is registered, we need to create the class that will be executed when someone calls this command. RubyGems
will look for a class in rubygems/commands
, that
matches our command name. In our case, repo_command.rb
.
# lib/rubygems/commands/repo_command.rb
class Gem::Commands::RepoCommand < Gem::Command
def initialize
super("repo", "Open github repository")
end
def execute
end
end
We create our Gem::Commands::RepoCommand
, that extends from Gem::Command
. The execute
method is our guy, it’s the one that will be called when we run the command.
Again, that’s exactly how the builtin commands work. If you check this directory, you will see all these commands.
It’s also a great place to find inspiration and see how the commands that you use every day work.
Implementing the functionality
Implementing our functionality is just a matter of calling a command in this execute
method. I’ll just use the open
command here, that should work just for OS X, feel free to implement it the way you like.
def execute
repo = options[:args].first
system "open http://github.com/#{repo}"
end
Notice that we have this options
hash with some useful information, like the list of arguments we received. In this case, we just need the first one, that is the repository name.
And that should be it! Here’s the final structure that we should have:
├── Gemfile
├── LICENSE.txt
├── README.md
├── Rakefile
├── lib
│ ├── repo
│ │ └── version.rb
│ ├── rubygems
│ │ └── commands
│ │ └── repo_command.rb
│ └── rubygems_plugin.rb
├── repo-0.1.0.gem
└── repo.gemspec
Installing the plugin
Let’s install this plugin to make sure it works.
First, remove the old repo-0.1.0.gem
that we created before:
$ rm repo-0.1.0.gem
Then make sure all your files are tracked:
$ git add .
Rebuild you gem:
$ gem build repo.gemspec
And install the plugin:
$ gem install repo-0.1.0.gem
# Successfully installed repo-0.1.0
# Parsing documentation for repo-0.1.0
# Done installing documentation for repo after 0 seconds
# 1 gem installed
Now the repo
command should already be available:
$ gem repo ruby/ruby
# should open http://github.com/ruby/ruby in your browser
Extra
There are a few methods that you can override in your class to better explain how the command works. I couldn’t find them documented anywhere, but you can just check the base command class.
The methods that you can override have a comment explaining its purpose.
One example is the usage
method, that I probably don’t need to explain. These information are shown when someone runs gem help <command>
. You can check gem help install
for an example of a very well documented command.
In the RubyGems website you can find a list of plugins. There are certainly hundreds more out there, but this is a good list to start with and see how things are done.
Update
Since people seem to be more interested in building RubyGems
plugins than I thought, I decided to create a plugin generator. You can find it on my github.
It’s basically an automation for the things I covered here.
Interested in learning Kubernetes?
I just published a new book called Kubernetes in Practice, you can use the discount code blog to get 10% off.