Lear: A Shakespearian Command Line Utility
I love quirky command line utilities. Sometimes the “animal brain of Unix” will happily provide its own weirdness—the two-letter gang (ed, dc, et al.) is always a safe bet for doing something other than what you expect—but it’s the community-driven space where things really get wild.
Some of my favorites include fortune, cowsay, and lolcat, but sl has a special place in my heart. Whenever you mistype ls
, it plays an agonizingly slow animation of a slow locomotive making its way across your screen. And just to be extra annoying, it eats Ctrl+C
and Ctrl+D
(although you can still background it with Ctrl+Z
if you want to be a buzzkill). As I was becoming more proficient with Rust and the crate ecosystem, I wanted to contribute my own weird, useless utility to this canon of highly serious programs.
The Idea
lear
is a program that prints a random quote from King Lear every time you mistype clear
into your console. This is something that I do with some frequency as I am not a very good typist.
As you can see from this dramatic re-enactment based on real-life events, lear
works well, even in high pressure situations.
kyle@mbp » lear GONERIL [Aside] O, ho, I know the riddle.--I will go. As they are going out, enter EDGAR disguised EDGAR If e'er your grace had speech with man so poor, Hear me one word. ALBANY I'll overtake you. Speak. Exeunt all but ALBANY and EDGAR (Lr. 5.1.43-46) kyle@mbp » ^C kyle@mbp » ^C kyle@mbp » arghhhh bash: arghhhh: command not found kyle@mbp » _
For no practical reason, lear
also supports the ability to quote specific passages with the addition of a few command-line arguments
kyle@mbp » lear quote 5 3 383 386
ALBANY
The weight of this sad time we must obey;
Speak what we feel, not what we ought to say.
The oldest hath borne most: we that are young
Shall never see so much, nor live so long.
(Lr. 5.3.383-386)
lear
can be installed on MacOS via Homebrew or installed from source using cargo. Full installation instructions are available on GitHub.
Making Lear
The first challenge after coming up with the initial idea was finding a copy of King Lear that I could use. It almost goes without saying, but in order for lear
to be true to its name, it needed all of King Lear to be included as part of the final program—and if I couldn’t find a free, online copy to use… well this project was going to be dead in the water.
As much as I was dedicated to this project, typing all 27,000+ words of the play from a physical copy was going to be too much of a time commitment. Thankfully MIT has been hosting the complete works of Shakespeare online (since 1993? What??) and they’ve placed the HTML versions they created into the public domain. Sweet! Thanks Jeremy, that must have been a lot of typing and I’m very grateful that you did it several years before I was born because I don’t have the attention span to commit to that kind of thing. Yoink!
This was only the beginning of my text processing woes, though. As any machine learning enthusiast will tell you, after you’ve gotten your raw data, you need to standardize, sanitize, and normalize it. And in this case, the pre-netscape HTML had quite a few quirks. On the plus side, every line had its own name attribute with the act, scene, and line numbers! Thanks Jeremy! On the other hand, though, everything was composed of <a>
and <blockquote>
tags, with <p>
tags only being used for stage directions. What the heck, Jeremy!
<A NAME=speech13> <b>EDMUND</b> </a> <blockquote> <A NAME=1.1.31>Sir, I shall study deserving.</A><br> </blockquote> <A NAME=speech13> <b>GLOUCESTER</b> </a> <blockquote> <A NAME=1.1.32>He hath been out nine years, and away he shall</A><br> <A NAME=1.1.33>again. The king is coming.</A><br> <p><i>Sennet. Enter KING LEAR, CORNWALL, ALBANY, GONERIL, REGAN, CORDELIA, and Attendants</i></p> </blockquote>
I mean, uh—whaaa…?? Maybe I just don’t understand HTML. Jeremy is the MIT grad, I’ll keep my mouth shut.
That aside, it was fairly straightforward to throw together a python script to transform the HTML version into a JSON format that would be easier for me to work with. Not trivial, mind you, but doable without a huge investment of time.
Performance Considerations (No, Really)
The actual implementation of lear
was pretty uneventful. Once I had a serviceable format for the text of the play, all I had to do was pick some random numbers for the act, scene, and lines—then format everything and print it to the console. But after I had a basic version working, I started thinking about performance. Now, I know that for a joke application like this one performance doesn’t really matter, but part of what makes lear
amusing to me is the immediacy with which you’re punished for your mistakes. One second, you’re trying to clear some junk out of the terminal and then bang! Now there’s even more junk in the terminal, you squint to read it and remember that you installed lear
as a joke months ago, grumble to yourself, and move on with your life. It should be funny, but the seconds of interruption should be after the text is already on screen. If after accidentally triggering it took more than a fraction of a second for everything to run, it would quickly overstay its welcome and be perceived as a hinderance rather than just a funny inconvenience.
The program is structured so that each scene is its own JSON file, which at runtime is deserialized so that a few lines can be selected and printed. If these were actually stored as files separate from the application binary, we would need to spend time reading them into memory and dealing with file I/O, potentially across multiple operating systems. No thank you. Instead, since we know that we only want to read from these few files, we can use Rust’s include_str!
macro to turn these files into const
strings which are already stored in memory. It also means that lear can be distributed as a single static binary which doesn’t depend on any other files, which is nice.
Now when lear
runs, it picks the act and scene first—then only deserializes the JSON for that section as opposed to loading it in from a file or even worse: deserializing the entire play just to pick 5–7 lines. I suppose spending time parsing JSON is also time that could theoretically be saved, but the only alternative then would be inserting all of King Lear as a bunch of prefabricated structs in the source code. That would have been a nightmare to manage, and on top of that serde is already so fast that deserializing a single scene (which is quite small compared to the play as a whole) is a cost that can be eaten rather imperceptibly.
Distribution
Now that I had a working program, I had to find a way to make it easily shareable with others. Thankfully, cargo
makes it incredibly easy to distribute Rust binaries—on the condition that your end-users are okay with compiling them from source. So technically we can say that there’s cross-platform support. Cool.
The other main way that I get quirky CLI utilities is with Homebrew. That intimidated me because, although Ruby is a normal language used by cool people, I didn’t know the first thing about it, except that it was like Python but with a “more interesting” take on syntax. Thankfully someone else had already gone through the hassle of figuring out how to distribute Rust packages on homebrew and the process was relatively painless.
And that’s the lesson you should take away from this—be weird, steal from others, and do as little work as possible.