Making Interactive CLIs with Typer and Rich — Part 1: Introduction and Setup

Py Bash

--

Command line image
Photo by Jake Walker on Unsplash

Python is a language that much well suits the command-line interface than the Graphical Interface, and so in this post/series we will learn to create a CLI tool but not just any CLI tool, an interactive yet beautiful looking CLI, and it is going to adapt its beauty based on the terminal(You’ll see what I mean soon).

Intro

So, you found this in your feed but don't know what CLIs/terminals are? Don't worry, it's totally fine because we will be going through that now.

Terminals

Terminals might be much popular to you as Command Prompt(CMD) or Windows Terminal to you if you are a Windows user. But actually, CMD is a shell and not a terminal. While on Unix systems, it is often called the console. Nowadays we use terminal emulators, like Alacritty, to emulate text terminals for us.

A bootstrapped application to communicate with a shell (Credits: SoulNinja)

Shells

A shell is what a terminal helps us communicate with. For this post/series, it isn't that important.

CLI(Command Line Interface)s

A command-line interface or CLI tool is any tool that runs inside a terminal/shell, ls /dir are some common tools — these are used to list all directories and files on Linux(ls) and Windows(dir). In this series, we will make a similar tool that will allow us to search for files in a given directory using Regular Expressions or simple phrases.

Now we have to understand an important thing that will help us later in this series, which is the different types of terminals that exist and their color support.

Namely, there are 5 different color systems terminals support:

  • 4-bit color
  • 8-bit color
  • 16-bit color
  • 256-bit color
  • Truecolor(16.7million colors)

All modern systems, nowadays support Truecolor or at least 256-bit on some old devices.

Note: The new Windows Terminal(Win10 and above) supports Truecolor, however, the classic Command Prompt(Win8.1 and before) supports only a 16-bit color profile.

Now, when we will make our CLI tool it will use colors to look nice, and it would be really hard and lengthy for us to check and use color profiles manually so instead we will use the rich Python library which automatically converts and adapts to different color profiles including dumb terminals like CMD.

Installing Libraries

Let’s install rich and other required modules now.

Installing rich and other modules

After you make sure you installed all the modules successfully, Open your favorite code editor/IDE, in my case, I’ll be using Neovim and jump into the code. Make sure to make a file named research.py In whichever directory you are working. And we will need to remember this path for testing our CLI in the future.

Code Time

In this post, we will be making the very basic tool and upgrading it as we go on.

First, we import the modules.

import typer
import os

Next, we will initialize our CLI using the typer.Typer() class and make it so that when we run the file directly it runs the CLI.

cli = typer.Typer()if __name__ == "__main__":
cli()

Before the next step, let’s talk a little about how commands in typer work.

Typer uses decorators and function parameters to create commands. That is typer provides a decorator @cli.command() That we will use to pass our function into, the decorator then extracts the options and arguments of the CLI command from the function signature. Now, that you know that let’s dive in.

The query parameter will be the query to search for. For now, this will search in the current directory but in the next post, we will add another parameter to search in a user-provided path.

@cli.command()
def search(query: str):
items = [i for i in os.listdir()]
matches = []
for item in items:
if query in item:
matches.append(item)
if matches == []:
print("No match found!")
else:
print("Match(es) found!")
print("Match(es):")
for ind, item in enumerate(matches):
print(f"{ind+1}. {item}")

In the above code, we then create a list of the items in the current directory and store that list in items , we also declare matches with an empty list, this will be useful later for appending the matches. Then we loop over each item in items and if the provided query is present in the item, append it to the matches list. Then we check if matches in empty or not, if it is, we print that no match is found, otherwise, we print the matches by looping over matches using enumerate() which is an inbuilt python function returning a tuple of the index and item.

You might be wondering why we do, ind+1 inside the loop, that is because, in most languages like python, the indexes start from 0 .

Hence, out final code should look like this:

import typer
import os
cli = typer.Typer()@cli.command()
def search(query: str):
items = [i for i in os.listdir()]
matches = []
for item in items:
if query in item:
matches.append(item)
if matches == []:
print("No match found!")
else:
print("Match(es) found!")
print("Match(es):")
for ind, item in enumerate(matches):
print(f"{ind+1}. {item}")
if __name__ == "__main__":
cli()

To test it, you simply run the python file and type will give you amazing looking help command on itself! It will look like this!

code output
Example of output from the program

Thanks

So, that's it for today, hope to see you in the next post, till then bye!

--

--

No responses yet

Write a response