(Read part 1 of this blog series here)
I hadn’t worked with any kind of command-line library in Elixir before; I’d done a little bit of it with Python, and a decent bit more with Go (Cobra’s awesome), but I figured it wouldn’t be too difficult. After all, Elixir is a modern language with intuitive tooling and plenty of documentation. Guess what? I was right.
The OptionParser library made it super easy to include the necessary options; this is an area where Elixir’s keyword lists really shine. They’re commonly used for config options due to the way the coupled tuples pair keys and values, and it doesn’t take much parsing (pun intended) to understand what’s happening below:
options = [switches: [query: :string], aliases: [q: :query]]
This is what Elixir calls a keyword list, which iex
helpfully tells us is “a list of two-element tuples where the first element of each tuple is an atom”. So in the above list, we have two tuples as part of that list: {switches, [query: :string]}
, and {alias, [q: :query]}
. The trick here is that in each of those two tuples, the second element is yet another keyword list - for the first tuple, the keyword list identifies the expected atom and its type; in the second tuple, we get the alias for the first tuple’s atom.
Keyword lists can be a bit confusing at first, but they’re a great way to capture related data, which is exactly what we want when it comes to configurations. Technical details aside, that singe line (options = [switches: [query: :string], aliases: [q: :query]]
) gives us a clean and efficient way to specify our command-line options. At the moment, we’re essentially saying that the --query
flag will capture a variable assigned to options[:query]
, and that that value will be a string. Additionally, we’re specifying that the --query
flag can be shortened to -q
.
Now, the above is just a way to tell our CLI application what we expect in the way of flags at the command-line level. How do we actually build out and execute that command-line application?
Elixir uses something called an escript to help you build a CLI executable, and the docs tell us explicitly that in order to build this executable, we want to add the following to our mix.ex
file under project
:
escript: [main_module: Commandline.CLI]
Basically, we need to provide a Commandline.CLI
module, with a main
function that takes exactly one argument (our CLI arguments, conventionally named args
). It’s in this Commandline.CLI.main
function that we specify the options
, and proceed from there to parse them out.
With all of that, I’ve outlined the full Commandline.CLI
module. Note that the main
function gets the options
, they’re parsed out by our OptionParser
, and then we specifically call the GiphyScraper.search
module with the :string
argument assigned to our --query
flag. We store the results and print them out:
defmodule Commandline.CLI do
alias GiphyScraper
def main(args) do
options = [switches: [query: :string], aliases: [q: :query]]
{opts, _, _} = OptionParser.parse(args, options)
results = GiphyScraper.search(opts[:query])
IO.inspect results
end
end
That’s just the code. In order to actually get our CLI working, we need to build the executable, and then run it with the appropriate flag:
mix escript.build
./giphy_scraper -q patriots # (or --query patriots)
Beautifully, here are our results:
[
%GiphyScraper.GiphyImage{
id: "GIApR38ChsjNo8VJLO",
url: "https://giphy.com/gifs/patriots-new-england-patriots-pats-gopats-GIApR38ChsjNo8VJLO",
username: "patriots",
title: "Serious Slow Motion GIF by New England Patriots"
},
%GiphyScraper.GiphyImage{
id: "TumQhRCwI3cjEsdL8j",
url: "https://giphy.com/gifs/patriots-new-england-patriots-pats-gopats-TumQhRCwI3cjEsdL8j",
username: "patriots",
title: "Rock On Football GIF by New England Patriots"
},
%GiphyScraper.GiphyImage{
id: "MgvAb84AxaKSM4UJmS",
url: "https://giphy.com/gifs/patriots-2020-nfl-patriots-week-17-MgvAb84AxaKSM4UJmS",
username: "patriots",
title: "Happy Akeem Spence GIF by New England Patriots"
},
...
To recap:
Building out a CLI interface for your Elixir application is surprisingly straightforward (at least for this kind of simple task.)
- Add “
escript: [main_module: Commandline.CLI],
” to yourmix.ex
file underproject
- Create a
Commandline.CLI
module in yourlib
folder, making sure that themain
function within the module has an argument (aritymain/1
) - Build out your required flags as a keyword list, and parse them out using
OptionParser.parse
- Proceed with whatever business logic is necessary for your application
- Build your executable with
mix escript.build
, and then run it with whatever arguments you outlined in step 3
Closing thoughts
This was, all in all, a fairly straightforward exercise, although I ended up taking a lot from a variety of Elixir forum posts as well as the documentation. I’ll be the first to admit that I don’t fully understand the inner workings of how everything is wired together. That said, I don’t really need to at this point in time. Interfaces and APIs exist for a reason, and the current Elixir ecosystem does a great job of giving you what you need while sheltering from some pretty ugly stuff. I found it pretty painless to add this CLI component to my application (see the updated version on GH), and it was made all the better by the fact that I didn’t have to touch a single other part of the existing Elixir work.
Happy trails!