Transformation: Parse the Command Line

Let’s start with the command line. We really don’t want to couple the handling of command-line options into the main body of our program, so let’s write a separate module to interface between what the user types and what our program does. By convention this module is called Project.CLI (so our code would be in Issues.CLI). Also by convention, the main entry point to this module will be a function called run that takes an array of command-line arguments.

Where should we put this module?

Elixir has a convention. Inside the lib/ directory, create a subdirectory with the same name as the project (so we’d create the directory lib/issues/). This directory will contain the main source for our application, one module per file. And each module will be namespaced inside the Issues module—the module naming follows the directory naming.

In this case, the module we want to write is Issues.CLI—it is the CLI module nested inside the Issues module. Let’s reflect that in the directory structure and put cli.ex in the lib/issues directory:

 lib
 ├── issues
 │   └── cli.ex
 └── issues.ex

Elixir comes bundled with an option-parsing library,[21] so we will use that. We’ll tell it that -h and --help are possible switches, and anything else is an argument. It returns a tuple, where the first element is a keyword list of the options and the second is a list of the remaining arguments. Our initial CLI module looks like the following:

project/0/issues/lib/issues/cli.ex
 defmodule​ Issues.CLI ​do
 
  @default_count 4
 
  @moduledoc ​"""
  Handle the command line parsing and the dispatch to
  the various functions that end up generating a
  table of the last _n_ issues in a github project
  """
 
 def​ run(argv) ​do
  parse_args(argv)
 end
 
  @doc ​"""
  `argv` can be -h or --help, which returns :help.
 
  Otherwise it is a github user name, project name, and (optionally)
  the number of entries to format.
 
  Return a tuple of `{ user, project, count }`, or `:help` if help was given.
  """
 def​ parse_args(argv) ​do
  parse = OptionParser.parse(argv, ​switches:​ [ ​help:​ ​:boolean​],
 aliases:​ [ ​h:​ ​:help​ ])
 case​ parse ​do
 
  { [ ​help:​ true ], _, _ }
  -> ​:help
 
  { _, [ user, project, count ], _ }
  -> { user, project, count }
 
  { _, [ user, project ], _ }
  -> { user, project, @default_count }
 
  _ -> ​:help
 
 end
 end
 end