Introduction to Thor

Share this article

256px-Ruby_logo.svg

Looking at RubyGem’s list of “Most Downloaded” gems, we notice lots of familiar names: “rake”, “rails”, “rack”, “actionmailer”, etc. But, “thor” is an exception; few of us have heard of it and even fewer have written code with it. So, what exactly is Thor?

It is a way to write powerful command line utilities in Ruby. It makes argument parsing really easy and specifies a certain format for command line arguments. Tons of Ruby projects use Thor to make writing command line utilities (for example, the “rails” command) easy, quick, and fun.

But, in order to understand why Thor is so awesome, we have to first understand what kind of problem it solves.

The Olden Days

Back when dynamic languages were meant to be for those who didn’t understand pointers, we had a procedure called getopt_long to parse command line options. The code required to actually use this function was generally a large switch statement which, once written, was to be forgotten. Until, of course, the day came when another option had to be added. Check out an example:

while (1)
         {
           static struct option long_options[] =
             {
               /* These options set a flag. */
               {"verbose", no_argument,       &verbose_flag, 1},
               {"brief",   no_argument,       &verbose_flag, 0},
               /* These options don't set a flag.
                  We distinguish them by their indices. */
               {"add",     no_argument,       0, 'a'},
               {"append",  no_argument,       0, 'b'},
               {"delete",  required_argument, 0, 'd'},
               {"create",  required_argument, 0, 'c'},
               {"file",    required_argument, 0, 'f'},
               {0, 0, 0, 0}
             };
           /* getopt_long stores the option index here. */
           int option_index = 0;

       c = getopt_long (argc, argv, "abc:d:f:",
                        long_options, &option_index);

       /* Detect the end of the options. */
       if (c == -1)
         break;

       switch (c)
         {
         case 0:
           /* If this option set a flag, do nothing else now. */
           if (long_options[option_index].flag != 0)
             break;
           printf ("option %s", long_options[option_index].name);
           if (optarg)
             printf (" with arg %s", optarg);
           printf ("\n");
           break;

         case 'a':
           puts ("option -a\n");
           break;

         case 'b':
           puts ("option -b\n");
           break;

         case 'c':
           printf ("option -c with value `%s'\n", optarg);
           break;

         case 'd':
           printf ("option -d with value `%s'\n", optarg);
           break;

         case 'f':
           printf ("option -f with value `%s'\n", optarg);
           break;

         case '?':
           /* getopt_long already printed an error message. */
           break;

         default:
           abort ();
         }
     }

Even if you’ve never looked at C code in your life, it doesn’t look like a pleasant experience. So, they came up with libraries to make this operation a little bit simpler. In the Perl community, “coming of age” includes writing your own command-opts parser. As it turns out, Perl’s heavy usage in command line utilities grew from its enviable GetOpts::Long module. Check out this example from Perldoc (Perl’s documentation system):

use Getopt::Long;
my $data = "file.dat";
my $length = 24;
my $verbose;

GetOptions("length=i" => \$length, 
          "file=s" => \$data,
          "verbose" => \$verbose) or die("Error in command line arguments");

Basically, this code allows you to specify length, data, and verbose options, along with numerical, string and boolean values, respectively. But, this still doesn’t seem right. It doesn’t feel very clean or object oriented because we’re just stuffing things into variables.

Thor is the “Ruby way” of doing command line option parsing. It makes everything wonderfully object-oriented and abstracts the details away. This allows you to concentrate on your grand idea instead of messing around with options and string parsing unnecessarily.

Let’s see a concrete example of how Thor works its magic.

First Steps with Thor

Let’s get straight to a simple example:

require 'thor'

class SayHi < Thor
  desc "hi NAME", "say hello to NAME"

  def hi(name)
    puts "Hi #{name}!"
  end
end

SayHi.start(ARGV)

If you run this without any arguments, you should get something like this as output:

Commands:
  first_steps.rb help [COMMAND]  # Describe available commands or one specific command
  first_steps.rb hi NAME         # say hello to NAME

With only a few lines of code, we have a full description of the command we’ve created! Of course, if you run the script with the arguments “hi Dhaivat”, you should get a response back saying “Hi Dhaivat!”. Let’s break down the code.

We created a class called “SayHi” that derives from the Thor class. Then, the next line talks about describing a specific command. The first argument to the “desc” function is “hi NAME”, which describes what kind of a command we want. In this case, it says that we want the user to be able to type in “hi” and then their name, which will be passed in as a variable. Another example would be “location LATITUDE LONGITUDE”, where the user can have pass in “location 64.39 21.34” and the location command will receive the latitude and longitude specified by those numbers.

What exactly do I mean by receive? Once we pass an argument, Thor figures out what kind of format it fits in and calls that method from our “SayHi” class. In this case, it calls the “hi” method with “name” as an argument.

Finally, we have our “hi” method which is just standard Ruby code.

Thor applications generally follow this sort of pattern; there are lots of features that can be used, but the general, underlying concepts remain the same.

Let’s write up a small utility, called file-op that has a command line option to output the contents of a file. Note: I’m not naming this utility “cat” because there are too many implementations of it in this world.

require 'thor'

class FileOp < Thor
  desc 'output FILE_NAME', 'print out the contents of FILE_NAME'

  def output(file_name)
    puts File.read(file_name)
  end
end

FileOp.start(ARGV)

It is quite nearly the same concept as the “SayHi” example, but this time if you run it with “output FILENAME”, it will output the contents of a file (which is handled through the “output” method of the “FileOp” class).

Digging a bit deeper

One of Thor’s most awesome features is the automatic “help generation”. The “desc” method used earlier has a second argument which actually describes what each command is designed to do. So, with our file-op utility, if run:

ruby file-op.rb help output

Thor prints out a nice usage notice:

Usage:
  file_op_v1.rb output FILE_NAME

print out the contents of FILE_NAME

Not only are we clearly documenting what each command does in our code, the user knows what each command does too!

Obviously, few significant commandline applications are going to be satisfied with such a basic options structure. Fortunately, Thor has our back there too. Wouldn’t it be nice if we could add a flag to our “output” command to output the file to stderr? I know, it wouldn’t really be all that nice, but let’s do it anyway:

require 'thor'

class FileOp < Thor
  desc 'output FILE_NAME', 'print out the contents of FILE_NAME'
  option :stderr, :type => :boolean

  def output(file_name)
    #options[:stderr] is either true or false depending
    #on whether or not --stderr was passed
    contents = File.read(file_name)
    if options[:stderr]
      $stderr.puts contents
    else
      $stdout.puts contents 
    end
  end
end

FileOp.start(ARGV)

We’ve added a few lines, but the most important is the option :stderr. This line tells Thor that whatever command we just defined (i.e. “output” in this case) can have a flag passed in as a boolean. In other words, it is either passed in or not passed in; it doesn’t have another value attached to it like --times 15 would. So, we can run:

ruby file-ops-v2.rb output --stderr filename

which would print the contents of “filename” to stderr.

But, this whole “option” thing makes Thor a bit odd. How does it know which command we’re adding the option to? Anytime you’re using option, it refers to the desc call just before it. Let’s add another command to our file-op utility that simply creates an empty file. This is equivalent to touch on *nix systems, so we’ll call the command “touch”:

require 'thor'

class FileOp < Thor
  desc 'output FILE_NAME', 'print out the contents of FILE_NAME'
  option :stderr, :type => :boolean
  def output(file_name)
    #options[:stderr] is either true or false depending
    #on whether or not --stderr was passed
    contents = File.read(file_name)
    if options[:stderr]
      $stderr.puts contents
    else
      $stdout.puts contents 
    end
  end

  desc 'touch FILE_NAME', 'creates an empty file named FILE_NAME'
  option :chmod, :type => :numeric
  def touch(file_name)
    f = File.new(file_name, "w")
    f.chmod(options[:chmod]) if options[:chmod]
  end
end

FileOp.start(ARGV)

The implementation is quite simple. All I’ve done is finished up with the output code and then placed the desc call for “touch”.

As an added bonus, I even included a --chmod option which allows you to set the permissions of the files you “touch” (e.g. passing in a 000 essentially creates a locked file). As a matter of personal preference, I don’t like the way Thor maintains the link between option and the method it affects, although I’m not sure of an alternative solution that would be as nice to use.

Class Options

What about commands that can be applied to any command? Something like:

some_utility.rb -v

Thor has us covered. These options, which are not associated with a specific command, are called class options. We could do with some more information when our utility runs. But, we usually don’t want this information, so we’ll include a verbosity option:

require 'thor'

class FileOp < Thor
  class_option :verbose, :type => :boolean
  desc 'output FILE_NAME', 'print out the contents of FILE_NAME'
  option :stderr, :type => :boolean
  def output(file_name)
    log("Starting to read file...")
    #options[:stderr] is either true or false depending
    #on whether or not --stderr was passed
    contents = File.read(file_name)
    log("File contents:")
    if options[:stderr]
      log("(in stderr)")
      $stderr.puts contents
    else
      log("(in stdout)")
      $stdout.puts contents 
    end
  end

  no_commands do 
    def log(str)
      puts str if options[:verbose]
    end
  end

  desc 'touch FILE_NAME', 'creates an empty file named FILE_NAME'
  option :chmod, :type => :numeric
  def touch(file_name)
    log("Touching file...")
    f = File.new(file_name, "w")
    f.chmod(options[:chmod]) if options[:chmod]
  end
end

FileOp.start(ARGV)

If you run this:

ruby file_op_v5.rb output some_file --verbose

You should see some log messages in addition to the file contents. Let’s take a look at the code.

We’ve added the log method, however, it is contained in a nocommands block. In order to tell Thor that log is not associated with a command we have to put it within this block. Within the log method, we print out the given string if Thor receives --verbose. We then use this method within the output and touch commands.

Wrapping It Up

Thor is an incredibly versatile library and makes command line parsing easy and intuitive. However, there are a few hiccups such as the nocommands block that one should be aware of to avoid potential gotchas.

I’ve used Thor in many different places, many times for short utilities to move some files around or search through some data. I have found it a very useful tool and hopefully you will, too.

The source for this article can be found here.

Frequently Asked Questions (FAQs) about Thor

What is Thor and how does it work?

Thor is a powerful tool for building self-documenting command line interfaces. It is written in Ruby and is used by Rails, Bundler, and many other projects. Thor includes a set of tasks (in the form of Ruby methods) that can be called from the command line. These tasks can be grouped into namespaces, making it easy to create complex command line interfaces.

How do I install Thor?

Thor can be installed as a Ruby gem. You can install it by running the command gem install thor in your terminal. Once installed, you can start using Thor to create your command line interfaces.

How do I create a new Thor script?

To create a new Thor script, you first need to require the Thor library in your Ruby script. Then, you can create a new class that inherits from Thor. Inside this class, you can define your tasks as methods. Each method will be available as a command in your command line interface.

How do I use Thor to parse command line options?

Thor provides a powerful option parser that can handle both short and long options, as well as options with or without arguments. To define an option, you can use the method_option method inside your task method. This will automatically add the option to the command line interface.

How do I group tasks in Thor?

Tasks in Thor can be grouped into namespaces. This can be done by creating a new class that inherits from Thor and defining your tasks as methods inside this class. The name of the class will be used as the namespace for the tasks.

How do I document my Thor tasks?

Thor tasks are self-documenting. This means that the description and usage information for each task is defined in the task method itself. You can provide a description for your task by passing a string to the desc method. The usage information can be provided by passing a string to the usage method.

How do I run a Thor task?

To run a Thor task, you need to call the task from the command line. The syntax for calling a task is thor namespace:task. If the task is not inside a namespace, you can call it directly with thor task.

How do I handle errors in Thor?

Thor provides a way to handle errors through the rescue method. This method can be used to catch and handle any exceptions that are raised during the execution of a task.

Can I use Thor with Rails?

Yes, Thor can be used with Rails. In fact, Thor is used by Rails to generate its command line interface. You can use Thor to create custom tasks for your Rails application.

How do I test my Thor tasks?

Thor tasks can be tested using any Ruby testing framework. You can test a task by calling it with a set of arguments and options, and then checking the output or the state of your application.

Dhaivat PandyaDhaivat Pandya
View Author

I'm a developer, math enthusiast and student.

Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week