I had an older post about ruby and slop but that’s with Slop 3 which is basically locked to Ruby 1.9.
No doubt, this post will bitrot too so please pay attention to the post date. The current ruby
is about 2.3.0, slop 4.3 is current, it’s 2016 and Trump is going to be our next president.
update:Oh god, he really is our next president …
It’s ok that you need help
I think the most confusing thing about slop is that it has great examples and documentation but
when you try to break this apart in a real app with small methods and single responsibilities
some things sort of get weird. I think this is because of exception handling as logic control
but I’m not sure enough to say slop is doing something wrong that makes this weird. In
I refer back to MY OWN BLOG quite often for slop examples so it’s ok that you need help.
Let’s look at the example from the README.
I disagree with -h here for hosts. I think -h should always be help. This is especially true
when switching contexts. When I switch to java or node or go or python, I have no idea
what those communities’ standards are. I rely on what unix expects: dash h.
I also disagree with the slop readme example because figuring out how to handle -h for help
is the most confusing thing about using slop because you have to use exceptions
as flow control (sort of an anti-pattern).
A real example
Let’s write a wrapper program called What the Fi? for our Internet connection.
When Internet things get wonky there are a few sites and tools I use to see is it just me?.
This wrapper will combine all those things into a CLI. We’ll use Slop 4.3 to parse
the CLI options. We’ll even write tests!
The main structure of this program is subcommand based. It’s a particular type
of CLI example similar to git where there are branches of main commands. After the
main branching logic, you could have options on each of the subcommands but I’ll leave that as an
exercise to you. I’d also recommend thor if you want to
build a complicated CLI with subcommands. What I mean to say is, the following
is just a CLI example that happens to follow this subcommand pattern.
Here’s how you use it.
The relevant Slop options are in this bit.
Notice that the rescue Slop::UnknownOption needed for Slop parsing is inside of a method called parse_arguments.
On many tools/projects I’ve done this isn’t enough to handle all cases. Then, what I’ll do
is roll a custom error class and throw that instead. You could also instead just not
begin;rescue;end here and do it higher up in the main. If you find yourself losing
data/context, it means you are at the wrong level of method calls. In the slop examples, this isn’t
explicitly mentioned but I find this way to be the most unixy. It print help on an unknown option
and it prints help if you don’t define -h or --help. If you have a -h option you want to use
then use the on '--help' example Slop mentions.
I hesitate to post the whole script here because it is very long. But here it is anyway. If you prefer
a git repo to puruse like a sane and reasonable person then here it is.
If you look at main and validate_arguments, you’ll see that --ip being a boolean and not
a string caused special logic to spew everywhere. It’s because it’s a switch and not a parameter with
a string value (it’s not --ip=184.108.40.206, it’s just --ip or nothing). Because of this, we have
to treat this option differently. Sometimes we need to know if it’s been set but because Slop
will set an unset boolean to false, we can’t check for nil like all the other flags.
I hope this post helps the googlers write their CLIs. My
older post about slop had
bit-rotted and at the same time gotten high up on the google rankings. I hope
I have avenged myself (against myself?). All hail the bit rot.
Thoughtbot has an excellent and much desired article on getting Docker + Rspec + Serverspec wired up but I couldn’t find anything about images generated from Packer. Packer generates its own images and so we can’t just build_from_dir(.). Our images are already built at that point. We’re using Packer to run Chef and other things beyond what vanilla Docker can do.
The fix is really simple after I was poking around in pry looking at the serverspec API.
First of all, what am I even talking about? Serverspec is like rspec for your server. It has matchers and objects like
So although we have application tests of varying styles and application
monitors, serverspec allows us to test our server just like an integration test
before we deploy. I had previously tried to go down this route with test
kitchen to test our chef recipes but it was sort of picky about paths.
Additionally, going with serverspec and docker doesn’t even require Chef. Chef
has already been run at this point! What this means is fast tests. Just
booting a docker image and running a command is fast.
So how does this work? Well, like I said the thoughtbot article is really good but I wanted to add to the
‘net about packer specifcally. The critical piece to make Serverspec work with a Docker image
created from Packer is in your spec itself (spec/yer_image_name/yer_image_name_spec.rb).
See that image = Docker::Image.get("yer_package_image") bit in the before block? This
is the difference between build my image (what the thoughtbot article uses)
and run an existing image. Since packer builds the image, we can just reuse
the one we have from our local store. Then later :docker_image, image.id sets
the image to use during the test. It knows about docker because of require "docker" from
serverspec. I’ll mention what versions of these gems I’m using at the time of this post
since this might bit-rot.
An idea that didn’t work
Ok this is cool! How about we have packer run our tests after the
packer build. Unfortunately this is mostly useless. :( The tests will run but
they won’t do anything if the tests fail.
Here’s the post-processor bit of our packer config. It just tells Packer
to do things after it’s done building. The first bit is tag our image so we can
push it out to our registry.
The path structure is arbitrary above. We have a project we’re currently
working on that I’ll explain in another blog post or talk. The only specifics
about this file structure is that typically you’d want to do something like
require 'spec_helper' but if you are building an image from a subdirectory
and then running tests from another nested subdirectory then you’ll need to
require_relative 'spec_helper'. I actually don’t know why this isn’t the
But like I said, running tests with Packer as a post processor doesn’t do
anything. You could run it with PACKER_DEBUG or something but I don’t like any
of that. I’ll be following up with a more complete workflow as we figure this
out. So you don’t need to do this last bit with the post-processors. I just
wanted to leave a breadcrumb for myself later.
If you are working on a gem that uses slop itself (your gem uses slop) then
you might run into this error when adding pry. Because the latest published
pry gem uses slop 3.6 but you are probably using slop 4. Slop 4 and 3 aren’t
the same API.
On bundle install you’ll probably get a different error.
This is true for pry 0.10.2 too. There are two options I’ve found that works:
tl;dr Do this
Install 0.10.3 or newer. Make sure your bundle is resolving to that exact version.
in your Gemfile. If you are working on a gem and
don’t really have a Gemfile but have a gemspec file then put this dev dependency in your gemspec.
Install From Master
You could also install pry from github master. This might show up as 0.10.3 depending on when
you are reading this. Version numbers only increment when pry does a release. I found
that pry git master did not have this issue.
Now the problem here is, if you are working on a gem yourself, you don’t have a Gemfile.
Afaik, you can’t install a gem from github source instead of a gemspec (that wouldn’t make sense
because you are going to distribute a gem!). But perhaps, you maybe want pry
temporarily in your gemspec like this:
Here’s how you can install a gem from source in a gemspec temporarily.
Now we have pry 0.10.3. Bundle doesn’t care it came from pry master. So when it
picks up on the spec.add_development_dependency it will install the version
you already have. BUT BIG PROBLEM You probably don’t want to commit this
because people will get the same error you got on bundle install if
that version doesn’t resolve. As far as I can tell, this pry version
works with slop so perhaps you just want to use 0.10.3 and be done with this.
I just wanted to illustrate how you can manipulate bundler.
Pry Vendored Slop
The reason this is happening is because of the slop namespace.
Pry fixed this in a commit associated with that issue. It’s fixed because they inlined
the gem as Pry::Slop so now Slop (your version) doesn’t conflict/activate.