This is the last in a two-part post on creating a Ruby cli gem from scratch. Part one found here.

Picking up from our last post, we want to start working on our user interface. In this case we want a cli that looks something like this:

```
#-> what would you like to do?
1 - Build a matrix
2 - Generate a random matrix
#-> now that we have a matrix, would you like to spiralize it?
1 - Spiralize it
2 - Stare at it
```

### Thor

We're going to use Thor, which is a toolkit for building command-line tools and applications. In fact, `Bundler`

itself was written using `Thor`

. Let's add it to the bottom of our `gemspec`

and re-install:

```
# spiralizer.gemspec
spec.add_dependency "thor"
---
# from cmd line:
> bundle install
```

Now, we just need to require it in and start using it!

```
require 'thor'
...
```

But, this is also a good time to start separating our code a bit, so we don't let things get to messy. So, let's create a file called `lib/spiralizer/cli.rb`

, and move our `require`

statement into this file. Then we will stub out a class under the `Spiralizer`

namespace and inherit from `Thor`

:

```
require 'thor'
require 'spiralizer'
class Spiralizer::CLI < Thor
end
```

Back in our main module we will need to require in this new file:

```
require "spiralizer/version"
require "spiralizer/cli"
module Spiralizer
...
end
```

Our cli is gem that users can install on their systems. We want them to have an executable that will take a simple command to spin up prompt we defined earlier:

Say the user enters `spiralizer go`

for example. We would want output similar to what we envisioned above:

```
$ >spiralizer go
#-> what would you like to do?
1 - Build a matrix
2 - Generate a random matrix
```

Thor gives us some easy-to-use tooling to get this thing going in no time. We'll get started by defining our `go`

method, and use the `desc`

macro annotation get some magic documentation:

```
require 'thor'
require 'spiralizer'
class Spiralizer::CLI < Thor
desc 'go', 'starts a prompt that brings users to spiral heaven'
def go
say "What would you like to do? (choose a number)\n 1 - Build a matrix\n 2 - Generate a random matrix"
action = ask '> '
say "\nyou picked #{action}"
exit!
end
end
```

`Thor`

provides some intuitive methods like `say`

to output something to the user, and `ask`

to prompt the user for input. They work just as expected. Notice that you can assign the result of `ask`

to a variable.

To start playing around with it, we just need to create a new directory called `exe`

(alternatively, we could have scaffolded our gem with a `--exe`

option but here we are). It should be a sibling directory of `lib`

and `bin`

. `cd exe && touch spiralizer`

to create a file. Then, give it executable permissions: `> chmod +x exe/spiralizer`

, and dump this inside:

```
#!/usr/bin/env ruby
require 'spiralizer/cli'
Spiralizer::CLI.start
```

And let's give 'er a whirl:

```
> bundle exec exe/spiralizer
Commands:
spiralizer go # starts a prompt that brings users to spiral heaven
spiralizer help [COMMAND] # Describe available commands or one specific command
```

Notice the helpful output Thor gives us. We have two commands available: `go`

and `help`

. Let's try them out:

```
> bundle exec exe/spiralizer help
Commands:
spiralizer go # starts a prompt that brings users to spiral heaven
spiralizer help [COMMAND] # Describe available commands or one specific command
> bundle exec exe/spiralizer go
What would you like to do? (choose a number)
1 - Build a matrix
2 - Generate a random matrix
> 1
you picked 1
```

So, `help`

is the default action, and we can see our new `go`

command is registered properly. Right now our prompt gives us two options, let's add functionality for our second option. When a user enters `'2'`

we want to spit out a random matrix. We can do a quick and dirty version of this and just hard code some matrix range/dimension pairs that we will feed to our matrix factory. Let's make a constant called `MATRICES`

that itself is a matrix:

```
MATRICES = [
['A-L', '4x3'],
['M-X', '3x4'],
['1-8', '2x4'],
['1-144', '12x12'],
['C-J', '4x2'],
['A-Z', '13x2']
].freeze
```

We now need a semi-random way to pick one of these: `range_response, dimensions = MATRICES[rand(6)]`

. This will give us a range value as a string like 'C-j' and dimensions like `4x2`

. We'll then split that range string up, so we can feed it into our factory:

```
range = range_response.split('-')
matrix = Spiralizer::Matrix.the_matrix(range: (range.first..range.last), dimensions: dimensions)
```

`Thor`

wants you to put helper methods for your class in a `no_commands`

block, so let's create one of those

and put all this together so its somewhat presentable. Here's what we have so far:

```
class Spiralizer::CLI < Thor
attr_reader :matrix
MATRICES = [
['A-L', '4x3'],
['M-X', '3x4'],
['1-8', '2x4'],
['1-144', '12x12'],
['C-J', '4x2'],
['A-Z', '13x2']
].freeze
desc 'go', 'starts a prompt that brings users to spiral heaven'
def go
say "What would you like to do? (choose a number)\n 1 - Build a matrix\n 2 - Generate a random matrix"
action = ask '> '
say "\n"
range, dimensions = random_range_and_dimensions
@matrix = Spiralizer::Matrix.the_matrix(range: range, dimensions: dimensions)
output_matrix
end
no_commands do
def random_range_and_dimensions
say "\ngenerating..."
sleep 1
range_response, dimensions = MATRICES[rand(6)]
range = range_response.split('-')
return (range.first..range.last), dimensions
end
def output_matrix
say "\nHere is your M A T R I X\n------------------------"
matrix.each { |inner| say inner.join("\t") }
exit!
end
end
end
```

You'll notice that any input we give the prompt at this point will just generate a random matrix from our list. We're making progress!

```
> bundle exec exe/spiralizer go
What would you like to do? (choose a number)
1 - Build a matrix
2 - Generate a random matrix
> 2
generating...
Here is your M A T R I X
------------------------
1 2
3 4
5 6
7 8
```

Next, let's hook up a follow up prompt that asks us if we want to spiralize the matrix. Update your `output_matrix`

method:

```
def output_matrix
say "\nHere is your M A T R I X\n------------------------"
matrix.each { |arr| say arr.join("\t") }
say "\nWould you like to spiralize it? (choose a number)\n 1 - Yes\n 2 - No"
action = ask '> '
say "\n"
if action == '1'
say Spiralizer::Spiralize.new(matrix: matrix).perform
end
exit!
end
```

We are now prompted for more input after the matrix has been generated! Pretty neat.

```
> bundle exec exe/spiralizer go
What would you like to do? (choose a number)
1 - Build a matrix
2 - Generate a random matrix
> 2
generating...
Here is your M A T R I X
------------------------
M N O
P Q R
S T U
V W X
Would you like to spiralize it? (choose a number)
1 - Yes
2 - No
> 1
m n o r u x w v s p q t
```

This is looking good but we still need to handle our first option to allow users to create their own matrix. Let's add a method to handle that input now:

```
def user_range_and_dimensions
range_response = ask("Please provide range. Acceptable format: (A-L) or (1-6)\n> ")
values = range_response.split('-')
dimensions = ask("Please provide dimensions. Acceptable format: (4x3) or (3x2)\n> ")
return (values.first..values.last), dimensions
end
```

We want this to trigger when the user chooses `'1'`

from our introduction prompt so we need to re-work our `go`

method:

```
def go
say "What would you like to do? (choose a number)\n 1 - Build a matrix\n 2 - Generate a random matrix"
action = ask '> '
say "\n"
case action
when '1' then range, dimensions = user_range_and_dimensions
when '2' then range, dimensions = random_range_and_dimensions
else exit!
end
range, dimensions = random_range_and_dimensions
@matrix = Spiralizer::Matrix.the_matrix(range: range, dimensions: dimensions)
output_matrix
rescue Spiralizer::InvalidInput => e
say "\nUh oh! #{e.message}"
ensure
say "\nexiting..."
exit!
end
```

You should be getting the hang of how to use `Thor`

. Our class is need of some cleanup, but with some refactoring we end up with something like this:

```
require 'thor'
require 'spiralizer'
class Spiralizer::CLI < Thor
MATRICES = [
['A-L', '4x3'],
['M-X', '3x4'],
['1-8', '2x4'],
['1-144', '12x12'],
['C-J', '4x2'],
['A-Z', '13x2']
].freeze
attr_reader :action, :matrix
desc 'go', 'starts a prompt that brings users to spiral heaven'
def go
clear_screen!
intro_prompt
build_matrix
output_matrix
action_prompt
spiralize!
rescue Spiralizer::InvalidInput => e
say "\nUh oh! #{e.message}"
ensure
quit_softly
end
no_commands do
def intro_prompt
say "What would you like to do? (choose a number)\n 1 - Build a matrix\n 2 - Generate a random matrix"
@action = ask '> '
end
def build_matrix
say "\n"
case action
when '1' then range, dimensions = user_range_and_dimensions
when '2' then range, dimensions = random_range_and_dimensions
else quit_softly
end
@matrix = Spiralizer::Matrix.the_matrix(range: range, dimensions: dimensions)
end
def user_range_and_dimensions
range_response = ask("Please provide range. Acceptable format: (A-L) or (1-6)\n> ")
values = range_response.split('-')
dimensions = ask("Please provide dimensions. Acceptable format: (4x3) or (3x2)\n> ")
return (values.first..values.last), dimensions
end
def random_range_and_dimensions
say "\ngenerating..."
pause_for_effect
range_response, dimensions = MATRICES[rand(6)]
range = range_response.split('-')
return (range.first..range.last), dimensions
end
def action_prompt
say "\nWhat would you like to do with it? (choose a number)\n 1 - Spiralize it\n 2 - Look at it"
@action = ask '> '
end
def output_matrix
say "\nHere is your M A T R I X\n------------------------"
matrix.each { |inner| say inner.join("\t") }
end
def spiralize!
say "\n"
say Spiralizer::Spiralize.new(matrix: matrix).perform if action == '1'
end
def clear_screen!
return system 'cls' if Gem.win_platform?
system 'clear'
end
def pause_for_effect
sleep 0.5
end
def quit_softly
say "\nexiting..."
pause_for_effect
exit!
end
end
end
```

Now, let's try it all out:

```
What would you like to do? (choose a number)
1 - Build a matrix
2 - Generate a random matrix
> 1
Please provide range. Acceptable format: (A-L) or (1-6)
> 1-6
Please provide dimensions. Acceptable format: (4x3) or (3x2)
> 2x3
Here is your M A T R I X
------------------------
1 2
3 4
5 6
What would you like to do with it? (choose a number)
1 - Spiralize it
2 - Look at it
> 1
1 2 4 6 5 3
exiting...
```

Our little gem is complete! There is a lot of cleanup we can and should do, but this is where this blog post ends.

Feel free to add new functionality and experiment further with Thor. I've gone ahead and added a `Crisscross`

class for

example in my repo.

Oh! One more thing, if yoy run `bundle exec rake install`

bundler will install this gem on your system as a global executable so you can then call it like this:

```
> spiralizer go
```

If you ever need to uninstall it for some insane reason just run `gem uninstall spiralizer`

.

You can find a finalized version of the code here.

Well, there you have it. We've built a cli gem from scratch using `Bundler`

and `Thor`

. I hope this post serves you well and until next time!