0
# User Interface Components
1
2
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.
3
4
## Capabilities
5
6
### Table System
7
8
Comprehensive table rendering with customizable styling, headers, rows, and formatting options.
9
10
```python { .api }
11
class Table:
12
def __init__(self, io: IO | Output, style: str | None = None) -> None:
13
"""
14
Create a table for output.
15
16
Args:
17
io (IO | Output): Output destination
18
style (str | None): Table style name
19
"""
20
21
def set_headers(self, headers: list[str]) -> Table:
22
"""
23
Set table headers.
24
25
Args:
26
headers (list[str]): Header row content
27
28
Returns:
29
Table: Self for method chaining
30
"""
31
32
def set_rows(self, rows: Rows) -> Table:
33
"""
34
Set table rows.
35
36
Args:
37
rows (Rows): Table row data
38
39
Returns:
40
Table: Self for method chaining
41
"""
42
43
def add_rows(self, rows: Rows) -> Table:
44
"""
45
Add rows to existing table data.
46
47
Args:
48
rows (Rows): Additional rows to add
49
50
Returns:
51
Table: Self for method chaining
52
"""
53
54
def add_row(self, row: list[str] | TableSeparator) -> Table:
55
"""
56
Add a single row.
57
58
Args:
59
row (list[str] | TableSeparator): Row data or separator
60
61
Returns:
62
Table: Self for method chaining
63
"""
64
65
def set_style(self, name: str) -> Table:
66
"""
67
Set table style by name.
68
69
Args:
70
name (str): Style name (compact, borderless, box, etc.)
71
72
Returns:
73
Table: Self for method chaining
74
"""
75
76
def get_style(self) -> TableStyle:
77
"""
78
Get current table style.
79
80
Returns:
81
TableStyle: Current style configuration
82
"""
83
84
def set_column_style(self, column_index: int, name: str) -> Table:
85
"""
86
Set style for a specific column.
87
88
Args:
89
column_index (int): Column index (0-based)
90
name (str): Style name
91
92
Returns:
93
Table: Self for method chaining
94
"""
95
96
def render(self) -> None:
97
"""Render the table to output."""
98
```
99
100
### Table Styling Components
101
102
Components for customizing table appearance and formatting.
103
104
```python { .api }
105
class TableStyle:
106
def __init__(self) -> None: ...
107
108
def set_horizontal_border_char(self, char: str) -> TableStyle: ...
109
def set_vertical_border_char(self, char: str) -> TableStyle: ...
110
def set_crossing_char(self, char: str) -> TableStyle: ...
111
def set_cell_header_format(self, format: str) -> TableStyle: ...
112
def set_cell_row_format(self, format: str) -> TableStyle: ...
113
def set_border_format(self, format: str) -> TableStyle: ...
114
def set_pad_type(self, pad_type: int) -> TableStyle: ...
115
116
class TableSeparator:
117
"""Represents a separator row in tables."""
118
pass
119
120
class TableCell:
121
def __init__(self, value: str = "", colspan: int = 1, style: TableCellStyle | None = None) -> None:
122
"""
123
Create a table cell with optional spanning and styling.
124
125
Args:
126
value (str): Cell content
127
colspan (int): Number of columns to span
128
style (TableCellStyle | None): Cell-specific styling
129
"""
130
131
class TableCellStyle:
132
def __init__(self, fg: str | None = None, bg: str | None = None,
133
options: list[str] | None = None, align: str = "left",
134
cell_format: str | None = None) -> None:
135
"""
136
Create cell-specific styling.
137
138
Args:
139
fg (str | None): Foreground color
140
bg (str | None): Background color
141
options (list[str] | None): Formatting options
142
align (str): Text alignment (left, center, right)
143
cell_format (str | None): Cell format template
144
"""
145
```
146
147
### Progress Indicators
148
149
Visual indicators for long-running operations with customizable formatting and progress tracking.
150
151
```python { .api }
152
class ProgressBar:
153
def __init__(self, io: IO | Output, max: int = 0) -> None:
154
"""
155
Create a progress bar.
156
157
Args:
158
io (IO | Output): Output destination
159
max (int): Maximum progress value
160
"""
161
162
def start(self, max: int | None = None) -> None:
163
"""
164
Start the progress bar.
165
166
Args:
167
max (int | None): Optional maximum value override
168
"""
169
170
def advance(self, step: int = 1) -> None:
171
"""
172
Advance progress by steps.
173
174
Args:
175
step (int): Number of steps to advance
176
"""
177
178
def set_progress(self, progress: int) -> None:
179
"""
180
Set progress to specific value.
181
182
Args:
183
progress (int): Current progress value
184
"""
185
186
def finish(self) -> None:
187
"""Finish and hide the progress bar."""
188
189
def clear(self) -> None:
190
"""Clear the progress bar from output."""
191
192
def display(self) -> None:
193
"""Display/refresh the progress bar."""
194
195
def set_format(self, format: str) -> None:
196
"""
197
Set progress bar format.
198
199
Args:
200
format (str): Format string for display
201
"""
202
203
def set_message(self, message: str, name: str = "message") -> None:
204
"""
205
Set a message to display with progress.
206
207
Args:
208
message (str): Message content
209
name (str): Message name/key
210
"""
211
212
def get_message(self, name: str = "message") -> str:
213
"""
214
Get a message by name.
215
216
Args:
217
name (str): Message name/key
218
219
Returns:
220
str: Message content
221
"""
222
223
@property
224
def progress(self) -> int:
225
"""Get current progress value."""
226
227
@property
228
def max_steps(self) -> int:
229
"""Get maximum steps."""
230
231
@property
232
def percent(self) -> float:
233
"""Get completion percentage."""
234
235
class ProgressIndicator:
236
def __init__(self, io: IO | Output, format: str | None = None,
237
indicator_change_interval: int = 100) -> None:
238
"""
239
Create a spinning progress indicator.
240
241
Args:
242
io (IO | Output): Output destination
243
format (str | None): Display format
244
indicator_change_interval (int): Milliseconds between indicator changes
245
"""
246
247
def start(self, message: str) -> None:
248
"""
249
Start the indicator with message.
250
251
Args:
252
message (str): Initial message to display
253
"""
254
255
def advance(self) -> None:
256
"""Advance the indicator animation."""
257
258
def finish(self, message: str, reset_indicator: bool = False) -> None:
259
"""
260
Finish the indicator with final message.
261
262
Args:
263
message (str): Final message
264
reset_indicator (bool): Whether to reset animation
265
"""
266
267
def set_message(self, message: str) -> None:
268
"""
269
Update the indicator message.
270
271
Args:
272
message (str): New message
273
"""
274
```
275
276
### Question System
277
278
Interactive prompts for gathering user input with validation and formatting options.
279
280
```python { .api }
281
class Question:
282
def __init__(self, question: str, default: Any = None) -> None:
283
"""
284
Create a question prompt.
285
286
Args:
287
question (str): Question text to display
288
default (Any): Default answer if user provides no input
289
"""
290
291
def ask(self, io: IO) -> Any:
292
"""
293
Ask the question and get user response.
294
295
Args:
296
io (IO): IO interface for interaction
297
298
Returns:
299
Any: User's answer or default value
300
"""
301
302
def hide(self, hidden: bool = True) -> None:
303
"""
304
Set whether input should be hidden (password input).
305
306
Args:
307
hidden (bool): Whether to hide input
308
"""
309
310
def set_hidden(self, hidden: bool) -> Question:
311
"""
312
Set hidden input and return self for chaining.
313
314
Args:
315
hidden (bool): Whether to hide input
316
317
Returns:
318
Question: Self for method chaining
319
"""
320
321
def set_validator(self, validator: Callable[[Any], Any]) -> Question:
322
"""
323
Set input validator function.
324
325
Args:
326
validator (Callable): Function to validate input
327
328
Returns:
329
Question: Self for method chaining
330
"""
331
332
def set_max_attempts(self, max_attempts: int) -> Question:
333
"""
334
Set maximum validation attempts.
335
336
Args:
337
max_attempts (int): Maximum number of attempts
338
339
Returns:
340
Question: Self for method chaining
341
"""
342
343
def set_normalizer(self, normalizer: Callable[[str], str]) -> Question:
344
"""
345
Set input normalizer function.
346
347
Args:
348
normalizer (Callable): Function to normalize input
349
350
Returns:
351
Question: Self for method chaining
352
"""
353
354
class ChoiceQuestion(Question):
355
def __init__(self, question: str, choices: list[str], default: Any = None) -> None:
356
"""
357
Create a multiple choice question.
358
359
Args:
360
question (str): Question text
361
choices (list[str]): Available choices
362
default (Any): Default choice
363
"""
364
365
def set_multiselect(self, multiselect: bool) -> ChoiceQuestion:
366
"""
367
Allow multiple choice selection.
368
369
Args:
370
multiselect (bool): Whether to allow multiple selections
371
372
Returns:
373
ChoiceQuestion: Self for method chaining
374
"""
375
376
def set_error_message(self, message: str) -> ChoiceQuestion:
377
"""
378
Set error message for invalid choices.
379
380
Args:
381
message (str): Error message template
382
383
Returns:
384
ChoiceQuestion: Self for method chaining
385
"""
386
387
class ConfirmationQuestion(Question):
388
def __init__(self, question: str, default: bool = True) -> None:
389
"""
390
Create a yes/no confirmation question.
391
392
Args:
393
question (str): Question text
394
default (bool): Default answer (True for yes, False for no)
395
"""
396
```
397
398
### UI Coordinator
399
400
Main UI component that provides access to all interactive elements from commands.
401
402
```python { .api }
403
class UI:
404
def __init__(self, io: IO) -> None:
405
"""
406
Create UI coordinator.
407
408
Args:
409
io (IO): IO interface for interaction
410
"""
411
412
def table(self, headers: list[str] | None = None, rows: Rows | None = None) -> Table:
413
"""
414
Create a table.
415
416
Args:
417
headers (list[str] | None): Optional table headers
418
rows (Rows | None): Optional table rows
419
420
Returns:
421
Table: Configured table instance
422
"""
423
424
def progress_bar(self, max: int = 0) -> ProgressBar:
425
"""
426
Create a progress bar.
427
428
Args:
429
max (int): Maximum progress value
430
431
Returns:
432
ProgressBar: Progress bar instance
433
"""
434
435
def progress_indicator(self) -> ProgressIndicator:
436
"""
437
Create a progress indicator.
438
439
Returns:
440
ProgressIndicator: Spinning indicator instance
441
"""
442
443
def ask(self, question: str | Question, default: Any | None = None) -> Any:
444
"""
445
Ask a question.
446
447
Args:
448
question (str | Question): Question text or Question object
449
default (Any | None): Default answer
450
451
Returns:
452
Any: User's answer
453
"""
454
455
def confirm(self, question: str, default: bool = True) -> bool:
456
"""
457
Ask a confirmation question.
458
459
Args:
460
question (str): Question text
461
default (bool): Default answer
462
463
Returns:
464
bool: User's confirmation
465
"""
466
467
def choice(self, question: str, choices: list[str], default: Any | None = None) -> Any:
468
"""
469
Ask a multiple choice question.
470
471
Args:
472
question (str): Question text
473
choices (list[str]): Available choices
474
default (Any | None): Default choice
475
476
Returns:
477
Any: Selected choice
478
"""
479
```
480
481
## Type Definitions
482
483
```python { .api }
484
# Type alias for table row data
485
Rows = list[list[str] | TableSeparator]
486
```
487
488
## Usage Examples
489
490
### Basic Table Display
491
492
```python
493
from cleo.ui.table import Table
494
495
class ReportCommand(Command):
496
def handle(self):
497
# Create and configure table
498
table = self.table()
499
table.set_headers(['Name', 'Status', 'Count'])
500
table.set_rows([
501
['Service A', 'Running', '150'],
502
['Service B', 'Stopped', '0'],
503
['Service C', 'Warning', '75']
504
])
505
table.render()
506
507
# Alternative: use table() helper
508
table = self.table(
509
headers=['ID', 'Task', 'Progress'],
510
rows=[
511
['001', 'Data Processing', '85%'],
512
['002', 'File Upload', '100%'],
513
['003', 'Validation', '45%']
514
]
515
)
516
table.render()
517
```
518
519
### Advanced Table Formatting
520
521
```python
522
class StatusCommand(Command):
523
def handle(self):
524
table = self.table()
525
526
# Styled headers
527
table.set_headers([
528
'<info>Service</info>',
529
'<comment>Status</comment>',
530
'<question>Uptime</question>'
531
])
532
533
# Mixed content with separators
534
table.add_row(['Web Server', '<info>✓ Running</info>', '5d 3h'])
535
table.add_row(['Database', '<error>✗ Stopped</error>', '0m'])
536
table.add_row(TableSeparator()) # Add separator line
537
table.add_row(['Cache', '<comment>⚠ Warning</comment>', '2h 15m'])
538
539
# Custom table style
540
table.set_style('box')
541
table.render()
542
543
# Column-specific styling
544
performance_table = self.table()
545
performance_table.set_column_style(2, 'right') # Right-align third column
546
performance_table.set_headers(['Metric', 'Value', 'Unit'])
547
performance_table.add_rows([
548
['CPU Usage', '45.2', '%'],
549
['Memory', '2.1', 'GB'],
550
['Disk I/O', '123.4', 'MB/s']
551
])
552
performance_table.render()
553
```
554
555
### Progress Bar Usage
556
557
```python
558
class ProcessCommand(Command):
559
def handle(self):
560
items = self.get_items_to_process()
561
562
# Create progress bar
563
progress = self.progress_bar(len(items))
564
progress.start()
565
566
for i, item in enumerate(items):
567
# Process item
568
self.process_item(item)
569
570
# Update progress with custom message
571
progress.set_message(f"Processing {item.name}")
572
progress.advance()
573
574
time.sleep(0.1) # Simulate work
575
576
progress.finish()
577
self.line('<info>Processing complete!</info>')
578
579
class DownloadCommand(Command):
580
def handle(self):
581
# Progress bar with custom format
582
progress = self.progress_bar()
583
progress.set_format('Downloading: %current%/%max% [%bar%] %percent:3s%% %message%')
584
585
files = ['file1.txt', 'file2.txt', 'file3.txt']
586
progress.start(len(files))
587
588
for file in files:
589
progress.set_message(f'Current: {file}')
590
# Simulate download
591
time.sleep(1)
592
progress.advance()
593
594
progress.finish()
595
```
596
597
### Progress Indicator for Indefinite Tasks
598
599
```python
600
class SyncCommand(Command):
601
def handle(self):
602
indicator = self.progress_indicator()
603
indicator.start('Synchronizing data...')
604
605
# Perform indefinite task
606
while self.sync_in_progress():
607
indicator.advance()
608
time.sleep(0.1)
609
610
indicator.finish('Synchronization complete!')
611
```
612
613
### Interactive Questions
614
615
```python
616
class SetupCommand(Command):
617
def handle(self):
618
# Simple text input
619
name = self.ask('Project name')
620
621
# Input with default value
622
port = self.ask('Port number', 8080)
623
624
# Hidden input (password)
625
password = self.secret('Database password')
626
627
# Confirmation
628
confirmed = self.confirm('Create project?', True)
629
if not confirmed:
630
self.line('<comment>Setup cancelled</comment>')
631
return 1
632
633
# Multiple choice
634
environment = self.choice(
635
'Environment',
636
['development', 'staging', 'production'],
637
'development'
638
)
639
640
# Multiple selection
641
features = self.choice(
642
'Select features (comma-separated)',
643
['authentication', 'caching', 'logging', 'monitoring'],
644
multiselect=True
645
)
646
647
self.line(f'<info>Creating {name} project for {environment}</info>')
648
return 0
649
```
650
651
### Advanced Question Validation
652
653
```python
654
class ConfigCommand(Command):
655
def handle(self):
656
# Question with validation
657
from cleo.ui.question import Question
658
659
# Email validation
660
email_question = Question('Email address')
661
email_question.set_validator(self.validate_email)
662
email_question.set_max_attempts(3)
663
email = self.ask(email_question)
664
665
# Port number validation
666
port_question = Question('Port (1024-65535)', 8080)
667
port_question.set_validator(lambda x: self.validate_port(int(x)))
668
port_question.set_normalizer(lambda x: x.strip())
669
port = self.ask(port_question)
670
671
def validate_email(self, email):
672
import re
673
if not re.match(r'^[^@]+@[^@]+\.[^@]+$', email):
674
raise ValueError('Invalid email format')
675
return email
676
677
def validate_port(self, port):
678
if not 1024 <= port <= 65535:
679
raise ValueError('Port must be between 1024 and 65535')
680
return port
681
```
682
683
### Combined UI Elements
684
685
```python
686
class DeployCommand(Command):
687
def handle(self):
688
# Confirmation before starting
689
if not self.confirm('Deploy to production?', False):
690
return 1
691
692
# Show deployment steps in table
693
steps_table = self.table(['Step', 'Status'])
694
steps = ['Build', 'Test', 'Package', 'Deploy', 'Verify']
695
696
for step in steps:
697
steps_table.add_row([step, 'Pending'])
698
steps_table.render()
699
700
# Execute with progress tracking
701
progress = self.progress_bar(len(steps))
702
progress.start()
703
704
for i, step in enumerate(steps):
705
self.line(f'<info>Executing: {step}</info>')
706
707
# Simulate step execution
708
time.sleep(1)
709
710
progress.set_message(f'Completed: {step}')
711
progress.advance()
712
713
progress.finish()
714
715
# Final status table
716
final_table = self.table(['Component', 'Status', 'URL'])
717
final_table.add_rows([
718
['Frontend', '<info>✓ Deployed</info>', 'https://app.example.com'],
719
['API', '<info>✓ Deployed</info>', 'https://api.example.com'],
720
['Database', '<info>✓ Updated</info>', 'N/A']
721
])
722
final_table.render()
723
724
self.line('<info>Deployment successful!</info>')
725
return 0
726
```