Enchant Your Terminal Application with Python: Survey, Glow, Rich, and Textwrap
The command-line interface (CLI) is a powerful tool for developers and system administrators. While often perceived as a stark and utilitarian environment, Python offers several libraries to dramatically enhance the user experience of your terminal applications. This article explores four key Python libraries – Survey, Glow, Rich, and Textwrap – that can transform your CLI programs from drab to dynamic. We’ll delve into each library’s features, demonstrate practical examples, and show you how to integrate them effectively to create engaging and user-friendly terminal applications.
Table of Contents
- Introduction to Terminal Application Enhancement
- Survey: Gathering User Input with Elegance
- What is Survey?
- Installation
- Basic Usage: Prompts and Questions
- Advanced Features: Validation and Customization
- Real-world Example: Configuration Script
- Glow: Markdown Rendering in Your Terminal
- What is Glow?
- Installation
- Basic Usage: Rendering Markdown Files
- Advanced Features: Theming and Customization
- Real-world Example: Displaying Documentation
- Rich: Adding Style and Structure to Your Terminal Output
- What is Rich?
- Installation
- Basic Usage: Printing with Color and Style
- Advanced Features: Tables, Progress Bars, and Logging
- Real-world Example: Monitoring a Process
- Textwrap: Formatting Text for Readability
- What is Textwrap?
- Installation (Standard Library – No Installation Required)
- Basic Usage: Wrapping and Filling Text
- Advanced Features: Indentation and Hanging Indents
- Real-world Example: Formatting Help Messages
- Combining the Libraries: A Powerful Synergy
- Example: A Configuration Tool with Rich Output
- Best Practices for Terminal Application Design
- Conclusion
1. Introduction to Terminal Application Enhancement
Traditionally, terminal applications have focused on functionality over aesthetics. However, a well-designed and visually appealing terminal application can significantly improve user experience, making it more intuitive and enjoyable to use. By leveraging Python’s powerful libraries, developers can create CLI tools that are not only functional but also visually engaging.
This article explores four libraries that each contribute a unique aspect to terminal application enhancement:
- Survey: Simplifies the process of gathering user input through interactive prompts and questions.
- Glow: Enables you to render Markdown files directly in the terminal, making it ideal for displaying documentation or help text.
- Rich: Provides a powerful toolkit for adding color, styling, and structure to your terminal output, enhancing readability and visual appeal.
- Textwrap: Offers robust text formatting capabilities, allowing you to wrap and indent text for optimal readability within the terminal window.
By mastering these libraries, you can create terminal applications that are both powerful and user-friendly, boosting productivity and overall user satisfaction.
2. Survey: Gathering User Input with Elegance
2.1 What is Survey?
Survey is a Python library that provides a fluent and intuitive interface for creating interactive command-line prompts. It simplifies the process of gathering user input by offering a variety of prompt types, including text input, password input, selection lists, and more. Survey handles the complexities of terminal interaction, allowing you to focus on the logic of your application.
2.2 Installation
You can install Survey using pip:
pip install terminal-survey
2.3 Basic Usage: Prompts and Questions
Here’s a basic example of using Survey to ask the user for their name:
from terminal_survey import ask
name = ask('What is your name?')
print(f'Hello, {name}!')
This code snippet will display a prompt asking the user for their name. The user’s input will be stored in the `name` variable. Survey automatically handles the terminal interaction, including displaying the prompt, reading the user’s input, and validating the input (if configured).
Here are some of the prompt types supported by Survey:
- Text: For accepting free-form text input.
- Password: For accepting sensitive information like passwords (input is masked).
- Select: For presenting the user with a list of choices to select from.
- Confirm: For asking a yes/no question.
- Path: For prompting the user for a file or directory path.
Example using `Select`:
from terminal_survey import ask
favorite_color = ask(
'What is your favorite color?',
type='select',
choices=['Red', 'Green', 'Blue']
)
print(f'Your favorite color is {favorite_color}!')
2.4 Advanced Features: Validation and Customization
Survey allows you to validate user input and customize the appearance of the prompts. You can define validation functions to ensure that the input meets specific criteria. You can also customize the prompt text, colors, and other visual elements.
Validation Example:
from terminal_survey import ask
def validate_age(value):
if not value.isdigit():
raise ValueError('Age must be a number.')
age = int(value)
if age < 0 or age > 120:
raise ValueError('Age must be between 0 and 120.')
return age
age = ask('What is your age?', validate=validate_age)
print(f'Your age is {age}.')
In this example, the `validate_age` function checks if the input is a valid number and if it falls within a reasonable age range. If the input is invalid, a `ValueError` is raised, and the user is prompted to enter the input again.
Customization Example:
from terminal_survey import ask
name = ask(
'What is your name?',
style={
'question': 'bold blue',
'answer': 'green'
}
)
print(f'Hello, {name}!')
This example customizes the style of the question and the answer using the `style` parameter. You can customize colors, fonts, and other visual attributes.
2.5 Real-world Example: Configuration Script
Here’s a more comprehensive example of using Survey to create a configuration script:
from terminal_survey import ask
def configure_application():
"""Configures the application by prompting the user for settings."""
app_name = ask('Enter the application name:')
db_host = ask('Enter the database host:', default='localhost')
db_port = ask('Enter the database port:', type='int', default=5432)
admin_email = ask('Enter the administrator email:')
enable_logging = ask('Enable logging?', type='confirm', default=True)
config = {
'app_name': app_name,
'db_host': db_host,
'db_port': db_port,
'admin_email': admin_email,
'enable_logging': enable_logging
}
print("\nConfiguration Summary:")
for key, value in config.items():
print(f"{key}: {value}")
confirm_save = ask('Save configuration?', type='confirm', default=True)
if confirm_save:
# In a real application, you would save the configuration to a file.
print("Configuration saved successfully!")
else:
print("Configuration not saved.")
if __name__ == "__main__":
configure_application()
This script prompts the user for various configuration settings and then displays a summary of the configuration. It then asks the user to confirm whether they want to save the configuration.
3. Glow: Markdown Rendering in Your Terminal
3.1 What is Glow?
Glow is a terminal-based Markdown renderer. It allows you to display Markdown files with syntax highlighting and formatting directly in your terminal. This is extremely useful for displaying documentation, README files, or any other Markdown content within your CLI applications.
3.2 Installation
While Glow itself is written in Go and typically installed as a standalone executable, we can access its functionality (or similar functionality) from Python using libraries like `markdown` and `rich`. Here’s how you can achieve Markdown rendering within your Python application:
pip install markdown rich
3.3 Basic Usage: Rendering Markdown Files
Here’s how to render a Markdown file using `markdown` and `rich`:
from markdown import markdown
from rich.console import Console
def render_markdown_file(filename):
"""Renders a Markdown file to the terminal using rich."""
try:
with open(filename, 'r') as f:
markdown_text = f.read()
except FileNotFoundError:
print(f"Error: File not found: {filename}")
return
html = markdown(markdown_text)
console = Console()
console.print(html) # Rich can render basic HTML
if __name__ == "__main__":
render_markdown_file("README.md")
This code snippet reads the contents of the `README.md` file, converts it to HTML using the `markdown` library, and then renders the HTML in the terminal using the `rich` library. Note that Rich’s HTML rendering capabilities are somewhat limited; you might need to preprocess the HTML for complex Markdown features.
3.4 Advanced Features: Theming and Customization
While `glow` as a standalone tool has its own theming capabilities, when integrating Markdown rendering within your Python application using `markdown` and `rich`, theming primarily relies on Rich’s theming and styling options.
You can customize the appearance of the output by using Rich’s styling tags and console configuration. For example, you can define custom styles for different elements of the Markdown output.
Example: Customizing Rich Styles
from markdown import markdown
from rich.console import Console
from rich.style import Style
def render_markdown_file_with_styles(filename):
"""Renders a Markdown file to the terminal with custom styles."""
try:
with open(filename, 'r') as f:
markdown_text = f.read()
except FileNotFoundError:
print(f"Error: File not found: {filename}")
return
html = markdown(markdown_text)
# Basic HTML to Rich markup conversion (more robust parsing may be needed)
html = html.replace("<", "<")
html = html.replace(">", ">")
html = html.replace("", "[bold blue]")
html = html.replace("
", "[/]")
html = html.replace("", "[bold green]")
html = html.replace("
", "[/]")
html = html.replace("", "")
html = html.replace("
", "\n")
html = html.replace("", "[on grey white]")
html = html.replace("
", "[/]")
html = html.replace("", "")
html = html.replace("
", "")
html = html.replace("", "- ")
html = html.replace(" ", "\n")
console = Console()
console.print(html)
if __name__ == "__main__":
render_markdown_file_with_styles("README.md")
This example demonstrates a basic approach to converting HTML tags generated by the `markdown` library into Rich’s markup for styling. A more sophisticated approach might involve parsing the HTML with a library like `BeautifulSoup` and then applying Rich styles based on the parsed HTML structure.
3.5 Real-world Example: Displaying Documentation
Here’s how you can use Glow (or the `markdown` and `rich` combination) to display documentation within your application:
def display_documentation(documentation_file):
"""Displays the application's documentation."""
render_markdown_file(documentation_file) # Using our render_markdown_file function
if __name__ == "__main__":
display_documentation("docs/user_manual.md")
This function takes the path to a Markdown file containing the application’s documentation and renders it in the terminal using the techniques described above.
4. Rich: Adding Style and Structure to Your Terminal Output
4.1 What is Rich?
Rich is a Python library that provides a rich set of features for adding color, styling, and structure to your terminal output. It allows you to create visually appealing and informative CLI applications with minimal effort. Rich supports features such as color themes, tables, progress bars, markdown rendering (though not as comprehensive as `glow`), syntax highlighting, and more.
4.2 Installation
Install Rich using pip:
pip install rich
4.3 Basic Usage: Printing with Color and Style
Here’s a basic example of using Rich to print text with color and style:
from rich import print
print("[bold red]This is bold red text![/]")
print("[link=https://www.example.com]Click here![/link]")
Rich uses a simple markup language to define styles. Styles are enclosed in square brackets. Common styles include `bold`, `italic`, `underline`, and color names.
4.4 Advanced Features: Tables, Progress Bars, and Logging
Rich offers a variety of advanced features, including:
- Tables: Create formatted tables with headers, columns, and data.
- Progress Bars: Display progress bars to indicate the progress of long-running tasks.
- Logging: Integrate Rich with Python’s logging module to produce styled log messages.
Table Example:
from rich.console import Console
from rich.table import Table
console = Console()
table = Table(title="Star Wars Movies")
table.add_column("Title", style="cyan", no_wrap=True)
table.add_column("Director", style="magenta")
table.add_column("Release Date", justify="right", style="green")
table.add_row("The Empire Strikes Back", "Irvin Kershner", "1980")
table.add_row("Return of the Jedi", "Richard Marquand", "1983")
table.add_row("The Force Awakens", "J.J. Abrams", "2015")
console.print(table)
Progress Bar Example:
import time
from rich.console import Console
from rich.progress import Progress
console = Console()
with Progress() as progress:
task1 = progress.add_task("[red]Downloading...", total=100)
task2 = progress.add_task("[green]Processing...", total=100)
task3 = progress.add_task("[cyan]Finishing...", total=100)
while not progress.finished:
progress.update(task1, advance=0.5)
progress.update(task2, advance=0.3)
progress.update(task3, advance=0.9)
time.sleep(0.01)
Logging Example:
import logging
from rich.logging import RichHandler
logging.basicConfig(
level="INFO", format="%(message)s", datefmt="[%X]", handlers=[RichHandler()]
)
log = logging.getLogger("rich")
log.info("Hello, World!")
log.warning("Something might go wrong!")
log.error("Something went wrong!")
4.5 Real-world Example: Monitoring a Process
Here’s how you can use Rich to monitor a long-running process with a progress bar and real-time updates:
import time
import random
from rich.console import Console
from rich.progress import Progress
def simulate_process(total_steps):
"""Simulates a long-running process."""
console = Console()
with Progress() as progress:
task_id = progress.add_task("[cyan]Processing...", total=total_steps)
for step in range(total_steps):
time.sleep(0.1) # Simulate work
progress.update(task_id, advance=1, description=f"[cyan]Processing step {step+1}/{total_steps}")
# Simulate some random errors/warnings
if random.random() < 0.05:
console.print("[yellow]Warning: Potential issue encountered at step {step+1}[/]")
if random.random() < 0.01:
console.print("[red]Error: Critical error occurred at step {step+1}[/]")
if __name__ == "__main__":
simulate_process(100)
print("[green]Process completed successfully![/]")
This example simulates a process with 100 steps. It displays a progress bar to indicate the progress of the process and prints random warnings and errors to the console.
5. Textwrap: Formatting Text for Readability
5.1 What is Textwrap?
The `textwrap` module is a built-in Python module that provides functions for wrapping and formatting text. It's particularly useful for ensuring that text is displayed neatly within the limited width of a terminal window.
5.2 Installation (Standard Library - No Installation Required)
The `textwrap` module is part of the Python standard library, so you don't need to install it separately. You can simply import it into your code.
5.3 Basic Usage: Wrapping and Filling Text
The two primary functions in `textwrap` are `wrap` and `fill`.
- `wrap(text, width=70, ...)`: Wraps the input `text` into a list of strings, where each string has a maximum width of `width` characters.
- `fill(text, width=70, ...)`: Wraps the input `text` and returns it as a single string, with line breaks inserted to ensure that each line has a maximum width of `width` characters.
Example: Wrapping Text
import textwrap
long_text = """
This is a very long piece of text that needs to be wrapped to fit within the terminal window.
The textwrap module provides functions for wrapping and filling text.
"""
wrapped_text = textwrap.wrap(long_text, width=60)
for line in wrapped_text:
print(line)
Example: Filling Text
import textwrap
long_text = """
This is a very long piece of text that needs to be wrapped to fit within the terminal window.
The textwrap module provides functions for wrapping and filling text.
"""
filled_text = textwrap.fill(long_text, width=60)
print(filled_text)
5.4 Advanced Features: Indentation and Hanging Indents
`textwrap` also provides features for adding indentation and creating hanging indents.
- `indent(text, prefix, predicate=None)`: Adds a given `prefix` to the beginning of selected lines in `text`. The `predicate` argument can be used to specify which lines should be indented.
- `hanging_indent` parameter in `wrap` and `fill`: Specifies the indent to add to all lines except the first line.
Example: Indentation
import textwrap
text = """
This is a block of text that needs to be indented.
Each line should be preceded by a specific prefix.
"""
indented_text = textwrap.indent(text, prefix="> ")
print(indented_text)
Example: Hanging Indents
import textwrap
long_text = """
This is a long text with a hanging indent. The first line starts at the left margin, and subsequent lines are indented.
This helps to visually separate the text from the surrounding content.
"""
filled_text = textwrap.fill(long_text, width=60, hanging_indent=" ")
print(filled_text)
5.5 Real-world Example: Formatting Help Messages
Here's how you can use `textwrap` to format help messages for your CLI applications:
import textwrap
def format_help_message(command, description, arguments):
"""Formats a help message for a command."""
help_message = f"""
Usage: {command} [options]
Description:
{textwrap.fill(description, width=70)}
Arguments:
"""
for argument, arg_description in arguments.items():
help_message += f" {argument}: {textwrap.fill(arg_description, width=60, subsequent_indent=' ')}\n"
return help_message
if __name__ == "__main__":
description = "This command performs a specific task. It takes several arguments and options to customize its behavior."
arguments = {
"filename": "The name of the file to process.",
"output_dir": "The directory to write the output to.",
"--verbose": "Enable verbose output."
}
help_message = format_help_message("my_command", description, arguments)
print(help_message)
This example formats a help message for a command, wrapping the description and argument descriptions to fit within the terminal window. It also uses a hanging indent for the argument descriptions to improve readability.
6. Combining the Libraries: A Powerful Synergy
The true power of these libraries lies in their ability to be combined to create truly exceptional terminal applications. By leveraging Survey for user input, Glow for documentation, Rich for styling, and Textwrap for formatting, you can create CLI tools that are both functional and visually appealing.
6.1 Example: A Configuration Tool with Rich Output
Here's an example that combines Survey and Rich to create a configuration tool with styled output:
from terminal_survey import ask
from rich.console import Console
from rich.table import Table
import textwrap
def configure_application():
"""Configures the application by prompting the user for settings with Survey and displaying the results with Rich."""
console = Console()
console.print("[bold blue]Application Configuration[/]")
app_name = ask('Enter the application name:')
db_host = ask('Enter the database host:', default='localhost')
db_port = ask('Enter the database port:', type='int', default=5432)
admin_email = ask('Enter the administrator email:')
enable_logging = ask('Enable logging?', type='confirm', default=True)
config = {
'app_name': app_name,
'db_host': db_host,
'db_port': db_port,
'admin_email': admin_email,
'enable_logging': enable_logging
}
console.print("\n[bold green]Configuration Summary:[/]")
table = Table(title="Configuration Settings")
table.add_column("Setting", style="cyan")
table.add_column("Value", style="magenta")
for key, value in config.items():
table.add_row(key, str(value)) # Convert to string for display
console.print(table)
confirm_save = ask('Save configuration?', type='confirm', default=True)
if confirm_save:
# In a real application, you would save the configuration to a file.
console.print("[green]Configuration saved successfully![/]")
else:
console.print("[red]Configuration not saved.[/]")
if __name__ == "__main__":
configure_application()
This script uses Survey to gather configuration settings from the user. It then uses Rich to display a styled table containing the configuration settings. Finally, it asks the user to confirm whether they want to save the configuration and displays a styled message based on their choice.
7. Best Practices for Terminal Application Design
Here are some best practices for designing user-friendly terminal applications:
- Keep it simple: Avoid overwhelming the user with too much information or too many options.
- Provide clear and concise messages: Use clear and concise messages to guide the user and provide feedback.
- Use color and styling effectively: Use color and styling to highlight important information and improve readability, but avoid overusing them.
- Provide helpful error messages: Provide informative error messages that help the user understand what went wrong and how to fix it.
- Use progress bars for long-running tasks: Use progress bars to indicate the progress of long-running tasks and provide feedback to the user.
- Consider accessibility: Ensure that your application is accessible to users with disabilities. For example, provide alternative text for color-coded information.
- Test thoroughly: Test your application thoroughly on different terminals and operating systems to ensure that it works as expected.
- Provide comprehensive documentation: Provide clear and comprehensive documentation that explains how to use your application.
- Use Textwrap for formatting long descriptions and help texts.
8. Conclusion
Enhancing your terminal applications with Python libraries like Survey, Glow, Rich, and Textwrap can significantly improve the user experience. By incorporating these libraries, you can create CLI tools that are not only functional but also visually appealing and user-friendly. This ultimately leads to increased productivity and user satisfaction. Experiment with these libraries and explore their features to create engaging and powerful terminal applications that stand out.
```