Using Thor and Ruby to build a CLI Sep 10 2018
Thor is a toolkit that can help us build command line interfaces(CLIs). You can find many tutorials on how to build a basic CLI using Thor. I want to explain the default behaviour of Thor and also when to use env
to define the binary that will run your script.
We normally build custom scripts to automate tasks on our servers or local environments. Often these scripts require many flags or a big list of parameters. If the number of flags and parameters (from now on, options) are small then we won’t have a problem building the logic to handle them. However, when there are a number of options or we want to handle aliases, e.g. accepting the flag --delete or -d as the same operation, then things can become harder and more tedious to maintain.
Table of Contents
Single file scripts
If you have a script with only a few options, you can keep everything in one file and run it as a Thor script. Let’s look at a script that displays a list of files in a directory sorted by the amount of disk space they occupy. This example illustrates the basic usage of Thor:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#!/usr/bin/env ruby
require "thor"
class DiskSpaceCLI < Thor
desc "show_stats", "Displays disk space stats"
method_option :path, aliases: "-p", default: "./"
method_option :count, aliases: "-c", default: "-1"
def show_stats
output = `du -s #{options[:path]}* `
files = output.split("\n")
files.sort! do |a,b|
a_size = a.split("\t").first
b_size = b.split("\t").first
a_size.to_i <=> b_size.to_i
end
count = options[:count].to_i
if count != -1 && count < files.size
files = files[0..(count-1)]
end
puts files
end
end
DiskSpaceCLI.start(ARGV)
Let’s look at the first line:
1
#!/usr/bin/env ruby
Of course we can use a specific ruby
binary, for example, /usr/bin/ruby
. However, if we want to use the ruby
command defined on the user $PATH
then we should use the env
command like the example script above. Or, if we use a version manager for ruby
(like rbenv
or rvm
) and we would like the script to always run the version selected by our version manager then we should use the env
command.
Now we add execution permissions to our script (chmod u+x [SCRIPT NAME]
) and test it.
If we run the script without any parameters, we would get the default behavior which is a list of options that the script supports. Thor, by default, calls the help
method. We can test this by over-writing the help method:
1
2
3
def help
puts "I'm the default method"
end
Now if you run the script without any parameters you’ll see our message displayed on the screen. If we want to add an additional message or functionality but we still want to display the previous help method then we should add a call to super
. For example,
1
2
3
4
def help
puts "Below you’ll see the options supported by the script"
super
end
This will display our message followed by the original help display.
Currently, if we want to run our show_stats
method we have to call the script and pass the name of our method, for example, to show the stats of the files in the current directory and only show the top 3 files we would run the following command:
1
./script_name show_stats --path=./ --count=3
That looks a little bit awkward, especially if it only has one method. We want to call our script in the following way:
1
./script_name --path=./ --count=3
That is, without show_stats
. In other words, we would like the script to have show_stats
as the default method. How do we do that? To do that, we use the default_task
macro. This allows us to set the task that will run by default. It will look something like this:
1
default_task :show_stats
Now we can run the script without having to explicitly tell it to run show_stats
method. Our script will look like the following snippet:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#!/usr/bin/env ruby
require "thor"
class DiskSpaceCLI < Thor
desc "show_stats", "Displays disk space stats"
method_option :path, aliases: "-p", default: "./"
method_option :count, aliases: "-c", default: "-1"
def show_stats
output = `du -s #{options[:path]}* `
files = output.split("\n")
files.sort! do |a,b|
a_size = a.split("\t").first
b_size = b.split("\t").first
a_size.to_i <=> b_size.to_i
end
count = options[:count].to_i
if count != -1 && count < files.size
files = files[0..(count-1)]
end
puts files
end
def help
puts "Below you'll see the options supported by the script"
super
end
default_task :show_stats
end
DiskSpaceCLI.start(ARGV)
Test it and let me know what you think. I think this post covers enough to get you started but if you would like to know more, send me a message and I will add more posts on how to use Thor and Ruby to build CLIs (for example, how to test Thor scripts using RSpec or how to package a Ruby program in a gem).