Cygwin Bash as a REPL inside The Console

In previous blogposts I discussed scripting custom REPLs for The Console. This time the custom will be just a good ol’ Unix shell called bash.

Agenda

  1. bind bash so it will respond to my commands and print results to output window
  2. try out auto-completion for command names

Please note, I use Windows, so I’ll bind bash which comes with Cygwin.

Just bind that bash, already

Boom.

The main trick here is to use java.lang.ProcessBuilder which will start bash.exe  and provide us with 3 streams: input, output, error. However, I call  redirectErrorStream to merge the error stream into output stream for simplicity.

What we lack here is definition of  listenToInput() function. By the way, note the nomenclature here. While process have input stream that receives input from user, it’s named as output stream because from ProcessBuilder  perspective the input data is pushed rather than pulled. The same goes with bash output (that gathers results of invoked commands) which from ProcessBuilder  perspective is input, because we’re going to read that, not opposite. It’s all reversed.

So, let’s listen to the ProcessBuilder  input (output + errors from bash):

Nothing special here. Whole thing runs in a thread. What bash outputs is being read and print on The Console –  line by line.

Why async?

There are at least two ways to do all of this:

  1. synchronously
  2. asynchronously

Above, you’ve seen the second. Why? Simplicity.

As you may notice, input and output streams are processed in two different threads. handleExecution() is called directly from some thread of The Console (probably JavaFX thread), while listenToInput()  works on another thread that is started by hand in init() .

The best and bullet-proof way would be to implement synchronous approach on top of queues. Why queues? Because of command completion.

Command name completion

this little scenario above is what we call auto-completion in bash. The result will be just this in command line:

And that’s my aim. Being in bash I want to only list command names that start with letters I already typed. Let’s extend our commandLineHandler  with completion:

And our scenario will work:

The handleCompletion()  implemented above only prints output from calling compgen -c pw .  It would be nice to change input text field, as auto-completion would do. However, output is collected on another thread so I don’t have output for that exactly input. That’s why I would need to synchronize management of input and output.

Queue: synchronize output with input

The main idea is queue every kind of input. In this situation it’s a input pushed on ENTER key and completion input pushed on TAB key:

The remaining job is to process all those inputs in a Thread. I won’t go into details but you can find full example code here -> The Console Wiki – bash REPL.

Summary

The plan of having a really custom REPL succeeded. This way I achieved an access to really big library of commands. For example, instead implementing custom md5 file hashing command, I could simply use cygwin built-in md5sum .

Auto-completion provided above is really simple. However, it’s not limited to this functionality. Bash supports programmable auto-completion of arguments for any command, however this is a a little bigger task to implement calling complete  command for argument completion.

References

  1. An introduction to bash completion
  2. NamekDev: Scriptable REPL in The Console
  3. NamekDev: Argument completion for commands in The Console
  4. The Console Wiki – bash REPL