Building a CLI gem from scratch in 2015
Tl;dr - Here’s the repo: gem_example. Use it to follow along as well.
I’m a big fan of the classic Throwaway Data Munging Ruby Script™. I have them littered about on multiple computers, often with multiple copies in different project’s folders, often with various tweaks to each.
Obviously this is a complete mess.
To make matters worse, many of these redundant ruby scripts (some of which are rakefiles or thor files) contain little hard-won gems that I’d really like to have handy next time I sit down to build something.
So as I do 1-2x a year, I decided to reel it in.
Packaging a script into a gem makes it accessible system-wide (e.g. no more copying scripts), and strongly encourages centralizing tweaks and using source control. Sounds like win-win-win to me.
I’ve built gems before, but often bury myself underneath trying to “Do It Right”, which supercedes the actual purpose of the gem and inevitably consumes all of the time I have available to actually build the thing.
So I figure, a quick-and-dirty starter is just the thing, especially because things have changed significantly from the last time I spun up a gem skeleton from scratch.
Standing on the shoulders of giants - Bundler 1.9 and Thor 0.19
If you’re building a CLI gem, you could do a lot worse than Thor. One of the stalwarts for this sort of thing, Thor gives you a powerful foundation for a CLI app, but also gently encourages the contemporary movement of “separate functionality from UI”. If we regard the CLI interface of our Gem as just a ‘UI’, we can greatly ease our development and testing burdens, and make our code easier to maintain. I’d strongly encourage adopting this mental distinction, and keeping the core functionality of our Gem from getting entangled into the CLI “interface”.
That said, we’ll use Thor to provide the CLI to use our gem, and Bundler to scaffold and build the thing.
A couple things I noticed right off the bat:
- Using the latest Bundler scaffold, Thor’s “executable” files no longer live in
/bin. Bundler has declared that executables will henceforth reside in
/exe, although there is still a
/bindirectory for other purposes.
- Git file tracking is used to run and package the gem. While this isn’t exactly brand new, it’s still important to realize you have to use source control (and commit new files!) before building your gem.
- These are some small tweaks that will make your gem-building experience much easier. We’ll discuss below.
The nitty gritty
Here are the basic steps I took, with some elaboration if pertinent:
cd into the starting parent directory
bundle gem gem_example
You will be prompted by Bundler to add a code of conduct and an MIT license
The first time through the gem wizard, Bundler may also ask you about including a testing scaffold. I prefer Rspec.
Remember, source control!
git add . && git commit -am 'initial commit'
Follow along here to see the state of the gem at this point.
Before going further, we should add the remainder of our testing scaffold. Remember to commit!
Thanks to the testing scaffold we added, we can now run tests with Rake:
bundle exec rake spec
Now we need to add our CLI scaffold and executable.
bundle install (to bundle Thor)
Don’t forget to make the exe executable:
chmod +x exe/gem_example
Also note that I updated the gemspec summary and description (this is necessary for both local gem installation and release).
Now we’re ready to add some actual functionality (or if you’re like me, you’re ready to paste in the code from that random ruby script).
I’m still sorting out my preferred approach to directory/module/class structures, so feel free to structure things however you like.
Now that our functionality is in place, we need to wire up our “UI” to it.
Again, be sure to commit your files at this point so that Bundler will find them.
Now you can run your gem like so, and get back Thor’s packaged help text:
bundle exec exe/gem_example
bundle exec exe/gem_example hello Johnny
bundle exec rake build (builds gem package)
bundle exec rake install (installs gem locally so you can use it anywhere on your system from the CLI)
rbenv rehash (if you use rbenv, run this so rbenv sees your newly installed gem’s executable and includes it in rbenv’s stub directory)
You can now call your gem natively from the terminal, just like any other gem:
gem_example hello Sally
So there you have it, hopefully up and running without much friction.
I will try to revisit this repo/post periodically and update it as new versions come out, because obviously things change and I’d like to have a current reference just as much as the next dev.