Dart for the terminal
Great, for the most part
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).
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
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.