On Command Line Applications in Swift

A meandering path to Terminus, an open source library.

Jonathancbadger
Geek Culture

--

In biology, animals like the crocodile are sometimes called living fossils because they appear to have changed little from specimens found in the geologic strata of the past. Computer technology has a few living fossils of its own. The terminal, or more likely terminal emulator, is one such example. Terminals from the 70’s like the VT100 were physical devices that had a keyboard, screen, and limited logic that would send and receive commands using a shared computer. Fast forward to 2022. Terminals still see a ton of use. Cloud based services, web serving, remote work, and scripting are few examples that come to mind. Today I would like tell a developer story that involves a quest for command line tools in Swift, an epic battle with ncurses, and ultimately the development of Terminus, an open source package that I hope some of you, the readers, will consider giving a try.

Packages for command line application development in Swift

After completing my training in biomedical informatics, where I heavily used the shell and languages like R and Python, I circled back to programming for Apple devices by learning Swift. I quickly came to realize that Swift is an amazing language and I wanted to see it grow into something that I could use in all my programming tasks, not just for writing iOS and Mac Apps. Sadly, the infrastructure to do other things in Swift still has a lot of maturing to do. I talked about this in depth in my last article, which I have linked below.

A lot of the things I like to do in the data science space (exploratory data analysis, data munging, machine learning, etc.) all take place on the command line. I feel at home when I have a script on the left hand side of my screen and iPython on the right.

Example working environment

For those unfamiliar, iPython is an interactive Python shell, or REPL (read-evaluate-print-loop) that runs in the terminal. It offers syntax highlighting, code completion, and a whole host of other nifty features. I thought to myself…you can do so many neat things in iPython with coloring and editing text, and the autocomplete menu system is really cool. What do we have in Swift that lets us make things splashy in the terminal?

After a bit of Googling I did manage to find some interesting packages for working on command line tools. Feel free to peruse the list below:

  • Rainbow — a nifty text coloring and styling package
  • ANSITerminal — offers text coloring and style, cursor functions (move, hide/show, save/restore), screen functions (clear screen, clear line, etc.), and keyboard capture (one character at a time, no line editor built in).
  • Swift CommandLineKit — from Matthias Zenger over at Google. Includes a system for handling command line arguments, text coloring and style, single and multi-line entry, text completion, and hints.
  • ConsolKit — from the makers of Vapor (a backend web framework in Swift). A robust package offering activity indications, argument handling (flags, options, etc.), text styling and coloring, logging, and more.

All of these packages offer basic support of text coloring and styling and ConsolKit and CommandLineKit have a ton of advanced features. The problem I ran into was that I wanted menus, fine grained support for moving the cursor around the screen, and better control of choosing colors. This led me to the famous ncurses C library.

My epic battle with ncurses

For those unfamiliar, ncurses is a C package originally written in the early 80’s designed to create user interfaces on a wide variety of terminals. Hundreds if not thousands of programs use ncurses to create textual user interfaces (TUIs). Since Swift plays so nicely with C I thought it would be a good idea to write a Swift wrapper around ncurses, which has some of the features such as menus that was interested in.

Image from https://linux.die.net/EVMSUG/ncurmigrate.html

In most cases, incorporating a C library into Swift is a relatively painless process. You provide a module map telling the compiler where your C library is located (in your local project or on your system) and what the name of your module should be when you use import in your wrapper files. From there you can start writing your wrapper around the C library in any way you like. My plan was to use Homebrew (apt on Linux) in my Package manifest and link my package with a system installed library of ncurses as in:

Every works great if you are on a Linux system. In fact, I started by looking at this project from TheCoderMerlin that does just that. But here is the nasty message I received from the compiler on my Mac:

Build error compiling ncurses on MacOS

Well crud. It turns out the Darwin module pulls in it’s own version of ncurses that’s included with the MacOS developer SDK! What this means is that the header files have already been imported and we are trying to redefine things that have previously been declared. In addition, the version of ncurses in the MacOS SDK is horribly out of date at version 5.8…which is circa 2011 as far as I can tell. Why?! I spent hours and hours on StackOverflow looking for fixes and just as I was about to go postal on my computer I came across a solution. In a nutshell you can pass -Xcc -D__NCURSES_H when you run swift build which tells the compiler to ignore all of the ncurses header files. The problem with that is that the same problem creeps up with the brew installed version of ncurses as well…and to fix that you have to copy all of the ncurses headers locally and replace instances of __NCURSES_H with something else.

To make this longish story short I finally got things to compile and work on both Mac and Linux, but all of the header swizzling would make maintenance a real pain and there was still a deal breaker. Any user interested using the wrapper package would have to include that -Xcc -D__NCURSES_H C compiler flag in their own projects. This would not make for a happy developer experience…so I archived the project and moved on. Rest in peace SwiftNCurses.

Terminus

Since that time I have written a purely ANSI based Swift package called Terminus that I would like to share with you now.

Here is sample code demonstrating how to write to the terminal with styling and color:

Text style and coloring output.

Notice you can add any number of styles and or colors to your text. Colors can be specified using RGB or using named colors from one of the built-in color palettes like the XTerm palette I used here. The documentation has a visual chart for each palette.

Color palette reference chart.

Terminus also supports AttributedStrings.

Using AttributedString

Now for some more interesting stuff. Here is some code for making a menu.

Menu demo

And last but not least…using a line editor that employs text highlighting.

Line editing demo

Conclusions

For those that stayed till the end thank you for reading! Terminus is a new package and by no means complete. I am actively looking for collaborators to add features, fix bugs, etc. If you would like to take Terminus for a test drive check out the GitHub repo. Documentation is also available on the SwiftPackageIndex. Lastly, if you enjoyed this article or want to provide some support it would be most appreciated:)

  • Follow me. Go ahead…tap that little green button!
  • Subscribe to receive e-mails when I write something new.

--

--

Jonathancbadger
Geek Culture

Pharmacist, data scientist, Apple developer, and maker.