CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-cleo

Cleo allows you to create beautiful and testable command-line interfaces.

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

ui.mddocs/

User Interface Components

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.

Capabilities

Table System

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."""

Table Styling Components

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
        """

Progress Indicators

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
        """

Question System

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)
        """

UI Coordinator

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 Definitions

# Type alias for table row data
Rows = list[list[str] | TableSeparator]

Usage Examples

Basic Table Display

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()

Advanced Table Formatting

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()

Progress Bar Usage

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()

Progress Indicator for Indefinite Tasks

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!')

Interactive Questions

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 0

Advanced Question Validation

class 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 port

Combined UI Elements

class 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 0

Install with Tessl CLI

npx tessl i tessl/pypi-cleo

docs

application.md

commands.md

exceptions.md

index.md

io.md

styling.md

testing.md

ui.md

tile.json