Dart for the terminal

Great, for the most part

Over the course of the past couple years of using Dart, it has quickly risen to be my language of choice. For the web, it is an easy choice compared to Javascript. Outside of the web, it is more about familiarity. Sure I could be writing in Java or Python, but Java isn’t very sexy, and duck-typed Python can be difficult to grok. Of course, I imagine I could write readable Python…but I’d just rather not go there. Dart is just as easy. Write a main function like good ol’ C, hand it off to the Dart interpreter, and you are up and running. Life is pretty damn great. The libraries however…are not revolutionary by any means and need a little help. To better explain this, without further ado, the following is my task at hand.

The Task

Every time I jump onto a Linux machine, I always get frustrated. All my favorite shell commands are not at reach, bash is looking pretty ugly, and don’t even get me started on how terrible Vim is by default. This is compounded ten fold when I get a new machine where I want to get my GUI just right. The solution: “dotfiles.” Everyone who is a serious Linux user has this. Long story short, we take all of our configuration files and throw them into a git repo. Tada! All of my configuration files are within reach. Except I want to take it a step further. I want to jump onto a new computer, load up my favorite distro, type a single command, and be at 100% productivity within 5 minutes. To do this I’ve written up a script that is unique to my needs to do such a thing. I have not quite reached the 5 minute mark yet, but I’m always getting closer. More to the point is that I’ve slimmed a month to a week and a week down to a day. Better yet, a single `git pull` means that the configurations of all my systems are kept constantly in sync.

Like all pieces of software, it started out simply enough, and has slowly grown in size. Bash scripts can only last for so long, so it was then that I moved into Python. Except eventually I decided I do not like Python. The real advantage to Python is that it is rare to come across a Linux machine that does not have Python installed by default, but almost inversely, I so rarely use Python that it started to annoy me. Off to Dart! A language I am familiar enough to write in without any syntax highlighting or error mumbo jumbo…not that I do not appreciate a good IDE (mmm Intellij).

The Problem

There ended up being an issue to this, how the hell does a person launch a process interactively in Dart? In a shell script, it is brain dead obvious, in Python it is relatively trivial, in Dart however, that does not come by default. It is possible to launch processes of course, but to have the user interact with them takes a bit more hackery. Before I share more, take a look at my wonderful solution for an interactive process:

Let us be honest with ourselves. It is a bit of a hack…no it is a hack. Having a static kill all switch that has to be remembered to be called at the end of program execution is not ideal in any shape or form.

Issue #1 — stdin is a stream

Having stdin as a stream actually does make perfect sense from an abstracted view of what the data from stdin is. However, stdin is not the stream of data, it is the descriptor to access the stream, and to top it off, with Dart, streams can only be listened to once before being dead. To close a stream and later try to reopen it results in a state error. This is particularly troublesome for anyone like me who wants to pipe stdin to multiple processes. The solution, broadcast streams!

Issue #2 — Broadcast streams are terrible

This API absolutely kills me to use. Why did they not make a new class that inherits from stream? Instead, streams and broadcast streams live in the same class making for a clunky guess by the developer if the stream can be listened to multiple times. Having an “isBroadcast” property on every stream is like something a person would find in Javascript. The number of times that I have come across where a stream could be found in a state of possibly being broadcast or not is 0. An API will return one or the other and it is up to the API author to document which it is…which is often never, but I digress. So here I am, I call asBroadcastStream on stdin to make a new stdin and forget about the old one. For every launched process, my newly broadcasted stdin is piped to it, and properly closed upon the process ending. Alls good in the neighborhood.

Issue #3—Broadcast streams can’t shut up

The Dart process won’t cleanly exit however and requires an interrupt signal to be sent from the terminal. What is going on here? I quickly make a hunch. It would not be that damn broadcasted stdin would it now? It most certainly is. To reproduce, write a main function with only a broadcast stream to stdin. Boom! The funny thing about that too, in such an instance it is not documented how someone would go about unsubscribing from said stream. A reference to the StreamSubscription object is not given until a listener is attached to the newly created broadcast stream. That is a bit of a flaw. Beyond that, the process won’t exit because the broadcast stream holds a subscription to the original stream, and won’t automatically let go of it. The app gets stuck infinitely waiting on user input, and we must tell it to let go, even though it is obvious nothing is subscribed to the broadcast stream. So now a global state of this stream has been generated, and we must always remember to explicitly kill it when the app is ready to exit. I can not help but to cringe at that.

Anyhow, that is my gripe and hopefully I have been able to help some others out. As for the stdin flaw, how I would implement it is to have stdin be its own object and have a read function kind of like the File API that allows for grabbing a stream of the data when it is needed.

Like what you read? Give Dylan Powers a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.