run_me: Smart Selection of Command Line Tasks by File

Test-driven development (TDD) involves running automated tests. A lot. If you have a function with five tests, you might easily invoke your test runner 50 or even 100 times before all tests are passing. Any action we take that often is a natural candidate for automation.

Leaving aside automatic test running on file saving for the time being, the ideal automation for running tests is probably having your editor send the file name and line number to your test runner (e.g. RSpec). I know at one point, the common AO Vim configuration had a keyboard shortcut command to do just that.

But it’s Not Always that Simple

Defining a shortcut task that calls your test runner directly can break down in our modern polyglot world. Let’s say you are writing a React app with a Rails backend. You write unit tests for your React JavaScript with Mocha, unit tests for your Rails app with RSpec, and system tests with Cucumber.

Now we’ve got a bit of a problem. Namely, ‘mocha’, ‘rspec’, and ‘cucumber’ are separate commands, so they can’t readily be bound to the same “run my tests” keyboard shortcut. You can, of course, set up three separate shortcuts, but this “smells” a bit and requires you to stop and think which shortcut you want to invoke.

One “Run Task” Shortcut to Rule Them All

To eliminate this cognitive load, I’ve created the run_me command. Really, it is useful for any task where you might use different command line programs for the same semantic meaning (e.g. “build” or “execute”).

run_me takes three arguments:

  • config – the path to a JSON file which will match filenames with commands to run
  • file – the filename that you want to “run” in some way (test, build, execute)
  • line – an optional line number giving more context about what to run

The config file is really the interesting bit. run_me expects a JSON file with a list of “runners,” which first determine if the filename sent in matches, then use the filename to run another command. Showing an example is probably the easiest way to explain how it works:


{
  "runners": [
    {
      "regex": "spec\\.ts$", // file.match(/spec\.ts$/)
      "cmd": "yarn test ${file}"
    },
    {
      "regex": "features", // file.match(/features/)
      "cmd": "bundle exec cucumber ${fileLine(':')}"
    },
    {
      "regex": "spec", // file.match(/spec/)
      "cmd": "bundle exec rspec ${fileLine(':')}"
    }  
  ]
}

How it Works

run_me will use the input filename and the find the first runner whose regex matches. Then it looks at the cmd for that runner. The cmd should be a string which can be used with JavaScript template literals (in other words, escape with ${}). The following variables are available:

  • file – The filename that was passed in
  • line – The line number that was passed in (or null if none was passed in)
  • match – The RegExp match array (useful if you want to only send part of the filename in to your command)
  • fileLine(joinStr) – A function which can be invoked to return file joined with line number, using a join string of your choice. Useful to combine into commonly accepted formats like “filename.rb:32.” If line number was not provided, fileLine() will only return file, regardless of the value of joinStr.

Processing Output

run_me transfers execution to the cmd you provide, so output will appear as it comes in. And, of course, the output is available for automated parsing and handling if your editor supports it.

Give it a Whirl

run_me is available on npm. Give it a try, and see if you can make your repetitive tasks incur less cognitive load.