Cleo allows you to create beautiful and testable command-line interfaces.
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
The UI components system provides rich interactive elements for building sophisticated CLI applications. It includes tables for data display, progress indicators for long-running operations, and question prompts for user interaction.
Comprehensive table rendering with customizable styling, headers, rows, and formatting options.
class Table:
def __init__(self, io: IO | Output, style: str | None = None) -> None:
"""
Create a table for output.
Args:
io (IO | Output): Output destination
style (str | None): Table style name
"""
def set_headers(self, headers: list[str]) -> Table:
"""
Set table headers.
Args:
headers (list[str]): Header row content
Returns:
Table: Self for method chaining
"""
def set_rows(self, rows: Rows) -> Table:
"""
Set table rows.
Args:
rows (Rows): Table row data
Returns:
Table: Self for method chaining
"""
def add_rows(self, rows: Rows) -> Table:
"""
Add rows to existing table data.
Args:
rows (Rows): Additional rows to add
Returns:
Table: Self for method chaining
"""
def add_row(self, row: list[str] | TableSeparator) -> Table:
"""
Add a single row.
Args:
row (list[str] | TableSeparator): Row data or separator
Returns:
Table: Self for method chaining
"""
def set_style(self, name: str) -> Table:
"""
Set table style by name.
Args:
name (str): Style name (compact, borderless, box, etc.)
Returns:
Table: Self for method chaining
"""
def get_style(self) -> TableStyle:
"""
Get current table style.
Returns:
TableStyle: Current style configuration
"""
def set_column_style(self, column_index: int, name: str) -> Table:
"""
Set style for a specific column.
Args:
column_index (int): Column index (0-based)
name (str): Style name
Returns:
Table: Self for method chaining
"""
def render(self) -> None:
"""Render the table to output."""Components for customizing table appearance and formatting.
class TableStyle:
def __init__(self) -> None: ...
def set_horizontal_border_char(self, char: str) -> TableStyle: ...
def set_vertical_border_char(self, char: str) -> TableStyle: ...
def set_crossing_char(self, char: str) -> TableStyle: ...
def set_cell_header_format(self, format: str) -> TableStyle: ...
def set_cell_row_format(self, format: str) -> TableStyle: ...
def set_border_format(self, format: str) -> TableStyle: ...
def set_pad_type(self, pad_type: int) -> TableStyle: ...
class TableSeparator:
"""Represents a separator row in tables."""
pass
class TableCell:
def __init__(self, value: str = "", colspan: int = 1, style: TableCellStyle | None = None) -> None:
"""
Create a table cell with optional spanning and styling.
Args:
value (str): Cell content
colspan (int): Number of columns to span
style (TableCellStyle | None): Cell-specific styling
"""
class TableCellStyle:
def __init__(self, fg: str | None = None, bg: str | None = None,
options: list[str] | None = None, align: str = "left",
cell_format: str | None = None) -> None:
"""
Create cell-specific styling.
Args:
fg (str | None): Foreground color
bg (str | None): Background color
options (list[str] | None): Formatting options
align (str): Text alignment (left, center, right)
cell_format (str | None): Cell format template
"""Visual indicators for long-running operations with customizable formatting and progress tracking.
class ProgressBar:
def __init__(self, io: IO | Output, max: int = 0) -> None:
"""
Create a progress bar.
Args:
io (IO | Output): Output destination
max (int): Maximum progress value
"""
def start(self, max: int | None = None) -> None:
"""
Start the progress bar.
Args:
max (int | None): Optional maximum value override
"""
def advance(self, step: int = 1) -> None:
"""
Advance progress by steps.
Args:
step (int): Number of steps to advance
"""
def set_progress(self, progress: int) -> None:
"""
Set progress to specific value.
Args:
progress (int): Current progress value
"""
def finish(self) -> None:
"""Finish and hide the progress bar."""
def clear(self) -> None:
"""Clear the progress bar from output."""
def display(self) -> None:
"""Display/refresh the progress bar."""
def set_format(self, format: str) -> None:
"""
Set progress bar format.
Args:
format (str): Format string for display
"""
def set_message(self, message: str, name: str = "message") -> None:
"""
Set a message to display with progress.
Args:
message (str): Message content
name (str): Message name/key
"""
def get_message(self, name: str = "message") -> str:
"""
Get a message by name.
Args:
name (str): Message name/key
Returns:
str: Message content
"""
@property
def progress(self) -> int:
"""Get current progress value."""
@property
def max_steps(self) -> int:
"""Get maximum steps."""
@property
def percent(self) -> float:
"""Get completion percentage."""
class ProgressIndicator:
def __init__(self, io: IO | Output, format: str | None = None,
indicator_change_interval: int = 100) -> None:
"""
Create a spinning progress indicator.
Args:
io (IO | Output): Output destination
format (str | None): Display format
indicator_change_interval (int): Milliseconds between indicator changes
"""
def start(self, message: str) -> None:
"""
Start the indicator with message.
Args:
message (str): Initial message to display
"""
def advance(self) -> None:
"""Advance the indicator animation."""
def finish(self, message: str, reset_indicator: bool = False) -> None:
"""
Finish the indicator with final message.
Args:
message (str): Final message
reset_indicator (bool): Whether to reset animation
"""
def set_message(self, message: str) -> None:
"""
Update the indicator message.
Args:
message (str): New message
"""Interactive prompts for gathering user input with validation and formatting options.
class Question:
def __init__(self, question: str, default: Any = None) -> None:
"""
Create a question prompt.
Args:
question (str): Question text to display
default (Any): Default answer if user provides no input
"""
def ask(self, io: IO) -> Any:
"""
Ask the question and get user response.
Args:
io (IO): IO interface for interaction
Returns:
Any: User's answer or default value
"""
def hide(self, hidden: bool = True) -> None:
"""
Set whether input should be hidden (password input).
Args:
hidden (bool): Whether to hide input
"""
def set_hidden(self, hidden: bool) -> Question:
"""
Set hidden input and return self for chaining.
Args:
hidden (bool): Whether to hide input
Returns:
Question: Self for method chaining
"""
def set_validator(self, validator: Callable[[Any], Any]) -> Question:
"""
Set input validator function.
Args:
validator (Callable): Function to validate input
Returns:
Question: Self for method chaining
"""
def set_max_attempts(self, max_attempts: int) -> Question:
"""
Set maximum validation attempts.
Args:
max_attempts (int): Maximum number of attempts
Returns:
Question: Self for method chaining
"""
def set_normalizer(self, normalizer: Callable[[str], str]) -> Question:
"""
Set input normalizer function.
Args:
normalizer (Callable): Function to normalize input
Returns:
Question: Self for method chaining
"""
class ChoiceQuestion(Question):
def __init__(self, question: str, choices: list[str], default: Any = None) -> None:
"""
Create a multiple choice question.
Args:
question (str): Question text
choices (list[str]): Available choices
default (Any): Default choice
"""
def set_multiselect(self, multiselect: bool) -> ChoiceQuestion:
"""
Allow multiple choice selection.
Args:
multiselect (bool): Whether to allow multiple selections
Returns:
ChoiceQuestion: Self for method chaining
"""
def set_error_message(self, message: str) -> ChoiceQuestion:
"""
Set error message for invalid choices.
Args:
message (str): Error message template
Returns:
ChoiceQuestion: Self for method chaining
"""
class ConfirmationQuestion(Question):
def __init__(self, question: str, default: bool = True) -> None:
"""
Create a yes/no confirmation question.
Args:
question (str): Question text
default (bool): Default answer (True for yes, False for no)
"""Main UI component that provides access to all interactive elements from commands.
class UI:
def __init__(self, io: IO) -> None:
"""
Create UI coordinator.
Args:
io (IO): IO interface for interaction
"""
def table(self, headers: list[str] | None = None, rows: Rows | None = None) -> Table:
"""
Create a table.
Args:
headers (list[str] | None): Optional table headers
rows (Rows | None): Optional table rows
Returns:
Table: Configured table instance
"""
def progress_bar(self, max: int = 0) -> ProgressBar:
"""
Create a progress bar.
Args:
max (int): Maximum progress value
Returns:
ProgressBar: Progress bar instance
"""
def progress_indicator(self) -> ProgressIndicator:
"""
Create a progress indicator.
Returns:
ProgressIndicator: Spinning indicator instance
"""
def ask(self, question: str | Question, default: Any | None = None) -> Any:
"""
Ask a question.
Args:
question (str | Question): Question text or Question object
default (Any | None): Default answer
Returns:
Any: User's answer
"""
def confirm(self, question: str, default: bool = True) -> bool:
"""
Ask a confirmation question.
Args:
question (str): Question text
default (bool): Default answer
Returns:
bool: User's confirmation
"""
def choice(self, question: str, choices: list[str], default: Any | None = None) -> Any:
"""
Ask a multiple choice question.
Args:
question (str): Question text
choices (list[str]): Available choices
default (Any | None): Default choice
Returns:
Any: Selected choice
"""# Type alias for table row data
Rows = list[list[str] | TableSeparator]from cleo.ui.table import Table
class ReportCommand(Command):
def handle(self):
# Create and configure table
table = self.table()
table.set_headers(['Name', 'Status', 'Count'])
table.set_rows([
['Service A', 'Running', '150'],
['Service B', 'Stopped', '0'],
['Service C', 'Warning', '75']
])
table.render()
# Alternative: use table() helper
table = self.table(
headers=['ID', 'Task', 'Progress'],
rows=[
['001', 'Data Processing', '85%'],
['002', 'File Upload', '100%'],
['003', 'Validation', '45%']
]
)
table.render()class StatusCommand(Command):
def handle(self):
table = self.table()
# Styled headers
table.set_headers([
'<info>Service</info>',
'<comment>Status</comment>',
'<question>Uptime</question>'
])
# Mixed content with separators
table.add_row(['Web Server', '<info>✓ Running</info>', '5d 3h'])
table.add_row(['Database', '<error>✗ Stopped</error>', '0m'])
table.add_row(TableSeparator()) # Add separator line
table.add_row(['Cache', '<comment>⚠ Warning</comment>', '2h 15m'])
# Custom table style
table.set_style('box')
table.render()
# Column-specific styling
performance_table = self.table()
performance_table.set_column_style(2, 'right') # Right-align third column
performance_table.set_headers(['Metric', 'Value', 'Unit'])
performance_table.add_rows([
['CPU Usage', '45.2', '%'],
['Memory', '2.1', 'GB'],
['Disk I/O', '123.4', 'MB/s']
])
performance_table.render()class ProcessCommand(Command):
def handle(self):
items = self.get_items_to_process()
# Create progress bar
progress = self.progress_bar(len(items))
progress.start()
for i, item in enumerate(items):
# Process item
self.process_item(item)
# Update progress with custom message
progress.set_message(f"Processing {item.name}")
progress.advance()
time.sleep(0.1) # Simulate work
progress.finish()
self.line('<info>Processing complete!</info>')
class DownloadCommand(Command):
def handle(self):
# Progress bar with custom format
progress = self.progress_bar()
progress.set_format('Downloading: %current%/%max% [%bar%] %percent:3s%% %message%')
files = ['file1.txt', 'file2.txt', 'file3.txt']
progress.start(len(files))
for file in files:
progress.set_message(f'Current: {file}')
# Simulate download
time.sleep(1)
progress.advance()
progress.finish()class SyncCommand(Command):
def handle(self):
indicator = self.progress_indicator()
indicator.start('Synchronizing data...')
# Perform indefinite task
while self.sync_in_progress():
indicator.advance()
time.sleep(0.1)
indicator.finish('Synchronization complete!')class SetupCommand(Command):
def handle(self):
# Simple text input
name = self.ask('Project name')
# Input with default value
port = self.ask('Port number', 8080)
# Hidden input (password)
password = self.secret('Database password')
# Confirmation
confirmed = self.confirm('Create project?', True)
if not confirmed:
self.line('<comment>Setup cancelled</comment>')
return 1
# Multiple choice
environment = self.choice(
'Environment',
['development', 'staging', 'production'],
'development'
)
# Multiple selection
features = self.choice(
'Select features (comma-separated)',
['authentication', 'caching', 'logging', 'monitoring'],
multiselect=True
)
self.line(f'<info>Creating {name} project for {environment}</info>')
return 0class ConfigCommand(Command):
def handle(self):
# Question with validation
from cleo.ui.question import Question
# Email validation
email_question = Question('Email address')
email_question.set_validator(self.validate_email)
email_question.set_max_attempts(3)
email = self.ask(email_question)
# Port number validation
port_question = Question('Port (1024-65535)', 8080)
port_question.set_validator(lambda x: self.validate_port(int(x)))
port_question.set_normalizer(lambda x: x.strip())
port = self.ask(port_question)
def validate_email(self, email):
import re
if not re.match(r'^[^@]+@[^@]+\.[^@]+$', email):
raise ValueError('Invalid email format')
return email
def validate_port(self, port):
if not 1024 <= port <= 65535:
raise ValueError('Port must be between 1024 and 65535')
return portclass DeployCommand(Command):
def handle(self):
# Confirmation before starting
if not self.confirm('Deploy to production?', False):
return 1
# Show deployment steps in table
steps_table = self.table(['Step', 'Status'])
steps = ['Build', 'Test', 'Package', 'Deploy', 'Verify']
for step in steps:
steps_table.add_row([step, 'Pending'])
steps_table.render()
# Execute with progress tracking
progress = self.progress_bar(len(steps))
progress.start()
for i, step in enumerate(steps):
self.line(f'<info>Executing: {step}</info>')
# Simulate step execution
time.sleep(1)
progress.set_message(f'Completed: {step}')
progress.advance()
progress.finish()
# Final status table
final_table = self.table(['Component', 'Status', 'URL'])
final_table.add_rows([
['Frontend', '<info>✓ Deployed</info>', 'https://app.example.com'],
['API', '<info>✓ Deployed</info>', 'https://api.example.com'],
['Database', '<info>✓ Updated</info>', 'N/A']
])
final_table.render()
self.line('<info>Deployment successful!</info>')
return 0Install with Tessl CLI
npx tessl i tessl/pypi-cleo