Ultra: A Leiningen Plugin for a Superior Development Environment

...man, that's a long title. I should do something about that.

This post is a general announcement that Ultra is ready for feedback, usage, and even (gasp!) pull requests. Happy hunting.

Intended Workflow

I happen to use Vim, and even though I use fireplace I often keep other terminal windows open in which I'll use Leiningen to run a REPL client, tests, or other tasks. Ultra is designed to complement that workflow, and focuses on features such as colorization via ANSI escape codes, pretty-printed output, and better test feedback.

Consequently, Ultra may be of limited utility if you do everything in an emacs or Sublime REPL, where there are no ANSI codes, etc.

All I can say is: I did my best.

Background

About two months ago I decided I'd been writing Clojure long enough that the time had come to optimize my development environment. In particular, I had a couple of major frustrations with the REPL, test output, and stacktraces, all of which I assumed had already been solved by existing Leiningen plugins.

On the face of it, I was correct - other experienced developers had felt the same frustrations, and had produced plugins to solve those problems. Unfortunately, what I found was a wealth of good ideas spread out across many different repositories, not all of which were compatible with each other.

Ultra is an attempt to unify that landscape, provide a really exceptional set of defaults, and make some difficult things easier. It violates the Clojure principle of "small libraries doing single things really well", but: a foolish consistency is the hobgoblin of little minds.

Features

The REPL

My first frustration - and the one that started me down this doomed path - was the fact that the REPL doesn't pretty-print values by default. This turns out to be relatively easy to configure, but then I started thinking: well, what if I want colorized values as well?

Behold:

Delicious! I can't claim much credit for this, incidentally - Ultra basically nabs Whidbey's functionality and wraps it, with the addition of a colorscheme loader for .edn files.

Go star and fork that repo, yo.

Tests

Okay, here's where things start to get interesting, which is to say that this is where I actually started to pull my own weight.

After reading Jake McCrary's article comparing Clojure's various test libraries, I found myself inspired by expectations, and decided to crib heavily from its playbook while avoiding the commitment to its syntax.

As a result, Ultra radically modifies the core functionality of clojure.test/report - a change that affects all deftest forms, and even some other testing libraries' test forms.

What does that mean things look like now? Well, let's see - below is a sample test, the sort of thing that might easily come up:

Fairly straightforward, except that whoever wrote that test has a really weird taste in field names. What about a test where we're comparing objects of the wrong type?

Nice. Hopefully we can already tell the difference between a string and a long when one is printed with quotation marks and the other isn't, but hey, every little bit helps, right?

Let's look at something slightly meatier.

Ah, now that's actually helpful. Especially when looking at larger collections, visual comparison between the actual and expected forms can be a frustrating endeavor. And we're only getting started with Ultra's diffs - let's take look at maps next:

That's-a-spicy-meatball, amirite?! From here, it just gets better. Check out these vector diffs - they come with helpful hints!

Since vectors and lists satisfy the same core interfaces (at least as far as Ultra cares), what works for one works for the other:

Most of the diffs demonstrated thus far rely on clojure.data/diff, but for strings there's a lot more mileage to be gained by using Google's diff-match-patch (with kudos to difform and lein-difftest for the original Clojure implementations):

No more straining your eyes in an attempt to find that one character that was missing! Woohoo!

Stacktraces

Clojure stacktraces take a while to get used to. When I was first getting familiar with the language, it wasn't at all apparent which parts of the stacktrace were important; these days I just find myself doing a lot of scrolling before I'm able to zero in on the parts of a stacktrace that are actually relevant.

I expect my feelings on the subject of stacktraces - and stacktrace-related tooling - to continue to evolve, but in the near term I'm very happy with the increased readability I've gotten out of using Aviso's pretty stacktraces:

test stacktrace demo

Bonus points for the fact that they use the same colorscheme as everything else in Ultra.

...whatever, I can totally give myself bonus points. Get out of here!

Java objects

The last major feature to Ultra relates to Java interop, and my frustration with not being able to tell at a REPL how to interact with an object. Here Ultra uses hooks that trigger on an nREPL server's initialization process to import functions from hara, making them conveniently available:

There's quite a bit more functionality here, but it's probably faster for you to take a look at the Hara docs and the Ultra wiki page on the subject.

Conclusion

I tend to to have a "live and let live" attitude to life, and Ultra is no exception to that rule - it might be useful for you, or it might not. Personally, I've found it to be an incredibly powerful tool for my development flow, and my hope is that it'll prove to be similarly valuable for any Clojure developer with a similar workflow.

That being said, if you have thoughts or opinions on how I could make Ultra even better, I'd love to hear them! Make issues, open pull requests, or just tweet at me.

I won't bite.

Maybe.

Discuss this post on Hacker News or on Twitter (@venantius)

Thanks to Keith Ballinger (@keithba), Bill Cauchois (@wcauchois) and Harry Wolff (@hswolff) for reading drafts of this post, and to Allen Rohner (@arohner) and David Lowe for providing early feedback on Ultra.