Skip to main content

Command Palette

Search for a command to run...

I reinvented the wheel… Well actually the terminal.

It felt like cheating to just host those projects on repl-it.

Published
5 min read
I reinvented the wheel… Well actually the terminal.

This project was a blast to create! And quite possibly, one of my more challenging projects to date.

When going into it, I started with a pen and paper(well actually an iPad and pencil) and tried to picture how I wanted the request cycle to go for starting a command line interface project from a virtual terminal. I knew I needed a webpage, along with some AJAX to update the view and send inputs. I also knew that that, a virtual terminal couldn't be shared like a web server, I mean, what if there was only one virtual terminal shared between all users? That would be chaos.

So I had my work cut out for me.

A classy rewrite.

My first step was migrating the demo.coffeedust simple rack server to something a bit more usable. I didn't want to go rails since I had no use for really any of its features. The only thing I needed was a better routing system and sessions, so when I implemented the new terminal I could have controllers, routes and sessions up and running in no time. So I decided to go with Sinatra. The rewrite took a little longer than expected but in the long run, it made the implementation of the new terminal feature much easier.

Let's get to building some terminals.

Now to begin the tricky part, having one session = one new virtual terminal instance.

As I mentioned before, each user had to have their own terminal, with their own independent running project demo. So I started crafting a virtual terminal object and it's properties, mainly it's thread that the project will run on. I've previously dealt with multithreading in ruby before. Much like JavaScript, multithreading in ruby is concurrent, it operates on a single system level thread in a loop.

This thread would need to be responsible for dispatching commands and catching outputs. It would also need to keep track of the output history and somehow, it would need to sync up with the web controller to send the output to display on the webpage.

I was able to override the puts and gets methods on my project, that way I didn't have to do any major altering in my project's original code.

I overrode the gets method to where is doesn't even response to the Kernel, but instead to my terminal's project thread in a similar manor. Syncing all of this up was very technical, but I was able to mirror the functionality of those methods, so it communicated perfectly with my controller and web page.

The bugs.

There were plenty but I worked through them and it was a great learning experience for me.

A big issue I had was memory sharing between terminal sessions. Even though I had separated everything into different threads, I noticed that any objects that were created and stored in a separate file in the project, would not be scoped to that thread, but rather, it was scoped to that file, meaning you could have 2 instances of my Inventory Manager project running, but the .all class methods for non persistent storage, was all shared between instances because they required the same file. This would cause issues of objects being duplicated and removed in memory for all sessions. That wasn't going to work!

So I was able to override some of those storage methods, and assign them to arrays and hashes that were held in that thread instead of a file that was shared between everything.

Problem solved.(at least that one 😬)

What do you mean "command not found"? It worked on my system!

Now to run some implementation tests on the server, And… I found a strange bug. All of the virtual terminal code worked, however my project, Inventory Manager, was not recognizing commands when trying to view items. But it appeared to be random, and sometimes allowed for an item to be shown as it normally should.

After quite a while of some deep think debugging, I determined the issue was actually an error when calling a method, however because of try-catch implementation(it was my first project 😬) it would default to "command not found."

What was the error? Because I was using ObjectSpace and id2ref to find Items in memory (I know, again I plead first project) I was getting a "could not find object with that id" error, but only when an object was assigned an ID that was negative. I never had this issue on my computer because it has plenty of RAM that it never needed to assigned negative ID's, whereas the server, has much less RAM.

After going into a binding.pry, I was able to figure out that, for some reason, the object ID that was being passed to my search method, did not include the negative symbol (-). Turns out, the regex I had, only matched digits and not letters when sending that method. After updating the regex pattern, success, it all functioned properly. 👌

Overall, as I mentioned, the main reason I didn't use REPL-IT to run my CLI projects was because it felt like cheating. After all this is my portfolio site, there to demonstrate my skills. If possible, everything on that site should all be made by me. Also I really wanted to challenge myself, and I knew I would enjoy every minute of it, and I did!

Check demo.dust.coffee's innards here on GitHub: https://github.com/Coffee-Dust/demo-coffeedust-io

And don't forget to try it out yourself by starting the Virtual Terminal interactive demo at https://dust.coffee/projects/inventory_manager

1*0p5dTAhIwqF29WAQ9IX9zg.png

Screenshot of the virtual terminal running Inventory Manager on demo.dust.coffee