The easiest & awesomest way to build a CLI menu

We’ve all seen them time and time again. Command line applications are super common on linux systems and also on Mac. With Homebrew at the fingertips, I find it super easy and satisfying to work with the terminal on Mac. Especially at work when I have to work a lot with ssh on linux servers, the switching around is super easily done. Plus the terminal is super powerful, so it always comes in handy if you know how to use it well.

But this is not my attempt to trying to convince you to learn the terminal. I’m all about Swift development here and that’s why I want to focus on something I discovered the other day when building the first version of my first CLI application Focal.

Menus in CLI Applications

The thing about CLI applications – something that you most likely never really notice until you try to code one yourself – is that it is astonishingly hard to create a menu. My first attempt was trying this using enums and creating a state machine that changes states with the different entered commands. But this uses an incredible amount of lines of code that I thought there has to be a better way. Plus it’s a mess to maintain that code since you’ll have tons of if and else-if statements or switch-cases, depending on what you used.

I thought to myself: Swift has to have a better way to go about this, since there is always a more clever way to solve something than in other common languages in swift. That’s why I love it so much. Swift is like the Bob Ross of the programming languages. Creative and curly haired.

Anyways, I soon figured out a better solution and that’s what I wanted to show you in this post. The basis for this is dictionarys. If you’re already familliar with dictionaries, you might want to skip the next part of the article since it will not contain any new informations for you. If you’re not sure where I’m going with this, I recommend you read it carefully.

Dictionarys

Let’s just briefly cover what dictionaries do.

So dictionaries are basically like arrays, just that you don’t retrieve the object based on an index of the array, but with a key object. This means that you can use certain information as a key to a certain value.

By the way those are the most important things you need to know. There are keys, and values. Keys are the object that you put in as an identifiers for what you want to retrieve. The value is the object that is stored inside of the space for the key that you entered.

Now notice that both of the attributes are objects. This means that we can put anything in there that we want. For example we can create a dictionary that has a String as keys, and for example an UIImage as values. This would mean that we could enter a string into the dictionary – which is called just like an array, just with the string instead of the array index – and we would retrieve the UIImage object that is stored inside of there.

This would look like this:

var dictionary: [String: UIImage]
dictionary["Image 1"] = UIImage(named: "image_1")
dictionary["Image 1"] // This would retrieve the image from the dictionary.

That’s a really important thing to note, since this will be the main component of our CLI menu.

Closures

The second thing that we take advantage of, that is super handy in Swift, is closures. Sounds scary and daunting at first, but rest assured, it’s super easy. I promise!

I’m not going into detail of what closures are, but let’s just say that closures allow you to store functions – or generally pieces of code – in variables and constants. By now you might already guess where I’m going with this.

Juts one thing that I’ll note here is how to write the closure type. We know how to tell Swift what a String is. We know how to tell Swift what an Integer is. But how do we tell Swift that something is of a closure type?

var thisIsAClosure: () -> (Void)

That’s it. That’s how we do it. Just two sets of braces divided by the arrow and, in our case, the word „Void“ in the second set of braces. This is all that we need for this simple example. You can get more sophisticated then that but for now this’ll do.

Put them together

Now we just put them together. This is where we can take a look at more code and where it gets interesting. But first let’s take a look at what we need to to to create a main menu.

CLI Menu

Think of how a menu in a CLI works.

  1. You start the application and you’re prompted with a greeting message.
  2. You can type in any of the commands and the app executes it.
    1. There might be a submenu for certain commands.
  3. The application returns to the main menu.
  4. There’s a command that exits the application, otherwise it’ll run forever.

So basically what’s happening is, that the command line starts with a greeting message. That’s simple.

After that follows an endless loop where the application asks you to enter any valid command. If you type a valid command in, the application executes it and then returns to the main menu, which let’s you put in any valid command again.

Also you can exit the application with a certain command.

Dictionarys & Closures

The way I implemented this in Focal, was using the dictionaries and closures. We’re going to do this in this article with a very simple example. An application that can greet us with a custom message or exit.

First of, let’s import Darwin to be able to exit the application and then define the functions that we need.

import Foundation
import Darwin

func exitApplication() {
  exit(0)
}

func greetUs() {
  print("Hey man, how you're doing?")
}

That’s all the tools we need. Those are going to be our closures inside of the dictionary.

Now, lets define the dictionary that will contain our commands.

var commands: [String: () -> (Void)] = [:]

As you can see, the dictionary is just a regular variable. The type we use for the key is a String since the input we’ll take as a command will be coming from a readLine(). The value of the dictionary is  () -> (Void)  which means it’s going to be a closure with no return type.

Now, let’s add the commands we have to the „commands“ dictionary:

commands["exit"] = exitApplication
commands["greet"] = greetUs

That’s super easy. We just use the dictionary with the key we want to have like an array and then assign the closure to it. Swift will automatically create and append the key, value pairs to the dictionary for us. We don’t need to do more than that.

Now let’s greet the user with a welcome message:

print("Welcome to this CLI Application.")
print("--------------------------------")
print("  exit:  Kills the application.")
print("  greet: Greets you.")
print()

Finally, let’s get into the real interesting part. How do we handle the user input and execute the commands that are typed in?

First of, since we’re using darwin to exit the application, we can just create an infinite loop to not have to restart the application every time we entered a command.

while(true) {
    print("What do you want to do? Type it now:")

}

Now we need to handle the input. We can take that in by using the readLine() function. We’ll just put that inside of the while loop.

guard let input: String = readLine() else {
    print("Please enter something.")
    continue
}

As you can see we’re reading in the user input and storing it inside of a constant. If you’re not sure why I used the guard statement here, check out the free optionals module, that should get you started. Basically what we’re doing is, check if the user enters something or if the readLine() was empty.

Last – and most interesting – bit is the checking if the entered command is valid and then finally executing it. This code will go right below the end of the guards else block, at the end of the while loop.

if commands.keys.contains(input) {
  commands[input]!()
} else {
  print("Sorry I don't know that command.")
}

This is where the interesting stuff happens. First of, we take the commands dictionary and check if the keys contain the input that we read from the readLine(). If yes, we go into the commands dictionary and take the input – which is the command – as a key for the dictionary to get the closure that is associated with it.

You might ask yourself where that part is where we execute the closure once we’ve got it. That’s easy. Right behind the commands[input]! there’s a set of round braces. That’s the part where we tell the closure to be executed. Awesome right? Just two simple braces and everything works.

Now in the else block I just put a simple message that says that the command was invalid. This obviously only get’s executed when the keys of the commands don’t include the entered command, which would mean that the entered command is invalid.

Codefile

If you didn’t catch exactly where what code should go, here’s the finished code file you should end up with.

Wrapping up

So that’s to for this short example. The funny thing is, this is really handy because it is super easy to add new commands. You just need to create a new function that does what you want and store it inside of the dictionary. Now in Focal, I’ve gone the way to implement this in a way that the greeting message accounts for the new commands automatically by cycling through the keys of the dictionary – that way the list of commands that is presented to the user is always up to date – but that’s juts taking the idea further. The concept obviously stayed the same.

Hope this was helpful to you. Enjoy, and share your results with me! I’d love to see what you came up with this concept.

 

Ask Anything!

Do you have any questions? Ask them! I'll answer via Email as soon as I read it.

Not readable? Change text. captcha txt
0