Adding a CLI Layer to GiphyScraper

(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.)

  1. Add “escript: [main_module: Commandline.CLI],” to your mix.ex file under project
  2. Create a Commandline.CLI module in your lib folder, making sure that the main function within the module has an argument (arity main/1)
  3. Build out your required flags as a keyword list, and parse them out using OptionParser.parse
  4. Proceed with whatever business logic is necessary for your application
  5. 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!

Written by

Leo Rubiano

Reader, programmer, traveler. Experienced back-end dev proficient with Python, Go, Elixir, Ecto, and Postgres.