rusty-rich 0.3.0

Rich text and beautiful formatting in the terminal — a Rust port of Python's Rich library
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
# Progress

Progress bars and task tracking -- a Rust port of Python Rich's `progress.py` and `progress_bar.py`. The progress module provides a multi-task progress display system with customizable columns, automatic refresh, file download tracking, and iterator wrapping.

---

## Progress

`Progress` is the central orchestrator. It manages a list of `Task` values and renders them as rows, with one row per task, using either a default layout or custom column definitions.

```rust
use rusty_rich::Progress;

let mut progress = Progress::new();
```

### Configuration

| Field               | Type                          | Default  | Description                                   |
|----------------------|-------------------------------|----------|-----------------------------------------------|
| `auto_refresh`       | `bool`                        | `true`   | Whether to automatically refresh the display  |
| `refresh_per_second` | `f64`                         | `4.0`    | Refresh rate in Hz                            |
| `transient`          | `bool`                        | `false`  | Remove progress display on completion         |
| `columns`            | `Option<Vec<Box<dyn ProgressColumn>>>` | `None` | Custom column layout (None = default render) |

Use `with_columns()` to set a custom column layout:

```rust
progress.with_columns(vec![
    Box::new(SpinnerColumn::new()),
    Box::new(TextColumn::new("description")),
    Box::new(BarColumn::new()),
    Box::new(TaskProgressColumn::new()),
]);
```

### add_task

Add a new task and return its unique ID.

```rust
pub fn add_task(
    &mut self,
    description: impl Into<String>,
    total: Option<f64>,
) -> usize
```

- `description` -- a human-readable label for the task.
- `total` -- the number of steps expected. `None` means indeterminate (no bar, no percentage).

```rust
let task_id = progress.add_task("Downloading...", Some(100.0));
```

Task IDs start at 1 and increment monotonically.

### advance

Advance a task by a delta (positive number of steps completed).

```rust
pub fn advance(&mut self, task_id: usize, delta: f64)
```

```rust
progress.advance(task_id, 25.0); // 25 steps closer to completion
```

If the delta pushes `completed` past `total`, `completed` is clamped to `total`.

### update

Set a task's completed value directly.

```rust
pub fn update(&mut self, task_id: usize, completed: f64)
```

```rust
progress.update(task_id, 50.0); // set to exactly 50
```

### remove_task

Remove a task from the display entirely.

```rust
pub fn remove_task(&mut self, task_id: usize)
```

```rust
progress.remove_task(task_id); // task is gone from the output
```

### advance_bytes

Advance a task by a byte count (converts `u64` to `f64` internally).

```rust
pub fn advance_bytes(&mut self, task_id: usize, bytes: u64)
```

Useful when tracking I/O progress.

### track

Wrap an iterator with progress tracking. Returns a `TrackIterator` that reports progress as items are consumed.

```rust
pub fn track<I: IntoIterator>(
    &mut self,
    sequence: I,
    description: impl Into<String>,
    total: Option<f64>,
) -> TrackIterator<I::IntoIter>
```

If `total` is `None`, the iterator's `size_hint` upper bound (or lower bound) is used as the total.

```rust
for item in progress.track(0..100, "Processing", None) {
    // item: i32
    std::thread::sleep(std::time::Duration::from_millis(10));
}
```

See [TrackIterator](#trackiterator) below for details on the wrapper.

### open

Open a file and return a `ProgressFile` with a new task registered for tracking the read progress. The file size is used as the total.

```rust
pub fn open(
    &mut self,
    path: impl AsRef<std::path::Path>,
    description: impl Into<String>,
) -> std::io::Result<ProgressFile>
```

```rust
let mut pf = progress.open("data.bin", "Downloading data.bin")?;
let mut buf = Vec::new();
pf.read_to_end(&mut buf)?;
pf.sync(&mut progress); // update progress with bytes read
```

### wrap_file

Wrap an existing `std::fs::File` with progress tracking when you already have the file handle and know the total size.

```rust
pub fn wrap_file(
    &mut self,
    file: std::fs::File,
    total: u64,
    description: impl Into<String>,
) -> ProgressFile
```

```rust
let file = std::fs::File::open("archive.tar.gz")?;
let metadata = file.metadata()?;
let pf = progress.wrap_file(file, metadata.len(), "Extracting");
```

### render

Render all visible tasks to a string. Used by the `Live` display or for manual drawing.

```rust
pub fn render(&self, width: usize) -> String
```

---

## Task

A single tracked task within a `Progress` display.

```rust
use rusty_rich::Task;
```

### Fields

| Field         | Type                       | Description                                      |
|---------------|----------------------------|--------------------------------------------------|
| `id`          | `usize`                    | Unique task identifier                           |
| `description` | `String`                   | Human-readable label                             |
| `total`       | `Option<f64>`              | Total steps (`None` = indeterminate)             |
| `completed`   | `f64`                      | Steps completed so far                           |
| `visible`     | `bool`                     | Whether the task is rendered                     |
| `start_time`  | `Instant`                  | When the task was created                        |
| `fields`      | `HashMap<String, String>`  | Arbitrary key-value metadata for custom columns  |

Tasks are not constructed directly -- they are created via `Progress::add_task()`.

### progress

Return the fraction complete (0.0 -- 1.0). Returns 0.0 for indeterminate tasks.

```rust
pub fn progress(&self) -> f64
```

### elapsed

Return the `Duration` since the task was created.

```rust
pub fn elapsed(&self) -> Duration
```

### time_remaining

Estimate the remaining duration based on the progress so far. Returns `None` if no progress has been made yet or the task is indeterminate.

```rust
pub fn time_remaining(&self) -> Option<Duration>
```

The estimate is `elapsed / progress - elapsed` -- a linear extrapolation. This is most accurate for tasks with a steady, predictable rate.

### is_finished

Return `true` if `completed >= total` (tasks without a total are never finished).

```rust
pub fn is_finished(&self) -> bool
```

---

## ProgressBar

`ProgressBar` is a standalone bar renderer. It is used both as a building block inside `BarColumn` and directly for one-off bar rendering.

```rust
use rusty_rich::ProgressBar;
```

### Fields

| Field              | Type              | Default | Description                              |
|--------------------|-------------------|---------|------------------------------------------|
| `total`            | `Option<f64>`     | `100.0` | Total steps (`None` = indeterminate)     |
| `completed`        | `f64`             | `0.0`   | Steps completed                          |
| `width`            | `Option<usize>`   | `None`  | Bar width in characters                  |
| `complete_char`    | `char`            | `'█'`   | Character for the completed portion      |
| `remaining_char`   | `char`            | `'░'`   | Character for the remaining portion      |
| `pulse`            | `bool`            | `false` | If true, render a pulsing indeterminate bar |
| `complete_style`   | `Style`           | plain   | Style for completed portion              |
| `remaining_style`  | `Style`           | plain   | Style for remaining portion              |
| `pulse_style`      | `Style`           | plain   | Style for pulse cursor                   |

### Builder methods

| Method                            | Description                                |
|-----------------------------------|--------------------------------------------|
| `total(total: f64)`               | Set total steps                            |
| `completed(completed: f64)`       | Set completed steps                        |
| `width(width: usize)`             | Set bar width                              |
| `complete_style(style: Style)`    | Style for the filled portion               |
| `remaining_style(style: Style)`   | Style for the unfilled portion             |

### percentage

Get the progress as a fraction (0.0 -- 1.0).

```rust
pub fn percentage(&self) -> f64
```

### render

Render the bar to a string at the given width.

```rust
pub fn render(&self, width: usize) -> String
```

If `pulse` is true or `total` is `None`, an indeterminate bouncing-cursor animation is rendered instead of a filled bar.

```rust
let bar = ProgressBar::new()
    .total(100.0)
    .completed(67.0)
    .complete_style(Style::new().color(Color::parse("green").unwrap()));
println!("{}", bar.render(40));
// Example output: [██████████████████████████████░░░░░░░░░░░░]
```

---

## TrackIterator

An iterator wrapper returned by `Progress::track()`. It counts items as they are consumed, but does not automatically advance the associated progress task -- the calling code must drive that externally (typically by calling `progress.advance()` in a live render loop, or by letting `Live` redraw on each tick).

```rust
pub struct TrackIterator<I: Iterator> {
    pub progress_id: usize,
    // ...
}
```

| Method             | Return Type   | Description                        |
|--------------------|---------------|------------------------------------|
| `count()`          | `usize`       | Number of items yielded so far     |
| `total()`          | `f64`         | Expected total from size_hint      |

```rust
let mut progress = Progress::new();
let iter = progress.track(0..50, "Counting", Some(50.0));
for item in iter {
    // ...
}
```

---

## ProgressFile

A `std::fs::File` wrapper that counts bytes read so the progress can be synced back to a `Progress` instance.

```rust
use rusty_rich::ProgressFile;
```

### Methods

| Method                  | Return Type              | Description                                  |
|-------------------------|--------------------------|----------------------------------------------|
| `bytes_read()`          | `u64`                    | Bytes read so far                            |
| `total()`               | `u64`                    | Total file size                              |
| `task_id()`             | `usize`                  | The task ID associated with this file        |
| `sync(&self, &mut Progress)` | --                 | Update the progress task's `completed` field |
| `inner()`               | `&std::fs::File`         | Reference to the inner file                  |
| `inner_mut()`           | `&mut std::fs::File`     | Mutable reference to the inner file          |
| `into_inner(self)`      | `std::fs::File`          | Consume and return the inner file            |

It implements `std::io::Read`, so you can pass it to any function expecting `Read`.

```rust
let mut pf = progress.open("archive.iso", "Downloading")?;
let mut chunk = [0u8; 8192];
loop {
    let n = pf.read(&mut chunk)?;
    if n == 0 { break; }
    pf.sync(&mut progress); // update the bar
    // render or sleep as needed
}
```

---

## ProgressColumn trait

All column types implement the `ProgressColumn` trait:

```rust
pub trait ProgressColumn: std::fmt::Debug {
    fn render(&self, task: &Task, width: usize, elapsed: Duration) -> String;
}
```

- `task` -- the current task being rendered.
- `width` -- available character width for this column (can be ignored).
- `elapsed` -- time since the task started.

---

## Column types

There are 11 column types.

### BarColumn

Renders the actual progress bar.

```rust
use rusty_rich::BarColumn;

let mut col = BarColumn::new();
col = col.width(30);
col = col.complete_style(Style::new().color(Color::parse("cyan").unwrap()));
col = col.finished_style(Style::new().color(Color::parse("green").unwrap()));
```

| Builder method              | Description                         |
|-----------------------------|-------------------------------------|
| `width(w: usize)`           | Override bar width                  |
| `complete_style(s: Style)`  | Style for the filled bar portion    |
| `finished_style(s: Style)`  | Style for the remaining bar portion |

### SpinnerColumn

Shows an animated spinner while the task is running. When the task is finished, displays a checkmark ("✓") in green.

```rust
use rusty_rich::SpinnerColumn;

let col = SpinnerColumn::new();
```

| Builder method                    | Description                          |
|-----------------------------------|--------------------------------------|
| `style(s: Style)`                 | Style for the spinner frame          |
| `finished_style(s: Style)`        | Style for the completion indicator   |

The spinner uses the default `SPINNER_DOTS` animation (braille dots). The completion text defaults to "✓" styled in green.

### TextColumn

Displays a value from `task.fields` using a given format.

```rust
use rusty_rich::TextColumn;

let col = TextColumn::new("description")
    .format("{:>20}")
    .style(Style::new().bold(true));
```

| Builder method             | Description                              |
|----------------------------|------------------------------------------|
| `format(fmt: &str)`        | Format string (currently applied simply) |
| `style(s: Style)`          | Style for the rendered text              |

The column looks up `task.fields["key"]` and renders its value.

### TimeElapsedColumn

Shows how long the task has been running.

```rust
use rusty_rich::TimeElapsedColumn;

let col = TimeElapsedColumn::new();
```

Formats as `0:05` (minutes:seconds) or `1:02:30` (hours:minutes:seconds) as appropriate.

### TimeRemainingColumn

Shows estimated time remaining based on current progress.

```rust
use rusty_rich::TimeRemainingColumn;

let col = TimeRemainingColumn::new();
```

| Field                  | Type    | Default | Description                                      |
|------------------------|---------|---------|--------------------------------------------------|
| `elapsed_when_finished`| `bool`  | `false` | Show elapsed time instead of empty when finished |

Shows `?` when insufficient data is available to estimate. Uses the same linear extrapolation as `Task::time_remaining()`.

### TaskProgressColumn

Shows the percentage complete as text (e.g. " 42%").

```rust
use rusty_rich::TaskProgressColumn;

let col = TaskProgressColumn::new()
    .style(Style::new().color(Color::parse("yellow").unwrap()));
```

For indeterminate tasks (no total), renders nothing.

### MofNCompleteColumn

Shows "completed / total" as a pair of integers (e.g. "3/10").

```rust
use rusty_rich::MofNCompleteColumn;

let col = MofNCompleteColumn::new();
col.separator = " of ".to_string(); // customize separator
```

| Field       | Type     | Default | Description            |
|-------------|----------|---------|------------------------|
| `separator` | `String` | `"/"`   | Text between the counts |

For indeterminate tasks, shows only the completed count.

### FileSizeColumn

Shows the completed count formatted as a human-readable size (e.g. "1.5 MB").

```rust
use rusty_rich::FileSizeColumn;

let col = FileSizeColumn::new()
    .style(Style::new().color(Color::parse("blue").unwrap()));
```

Uses decimal (1000-based) units: B, KB, MB, GB, TB, PB.

### TotalFileSizeColumn

Shows the total size formatted as a human-readable size (e.g. "250.0 MB").

```rust
use rusty_rich::TotalFileSizeColumn;

let col = TotalFileSizeColumn::new();
```

For indeterminate tasks, renders nothing.

### DownloadColumn

Shows "completed / total" formatted as file sizes (e.g. "500.0 KB / 1.5 MB").

```rust
use rusty_rich::DownloadColumn;

let col = DownloadColumn::new()
    .style(Style::new().color(Color::parse("cyan").unwrap()))
    .separator(" of ");
```

| Field       | Type     | Default | Description            |
|-------------|----------|---------|------------------------|
| `separator` | `String` | `"/"`   | Text between the sizes |

### TransferSpeedColumn

Shows transfer speed in human-readable format (e.g. "1.5 MB/s").

```rust
use rusty_rich::TransferSpeedColumn;

let col = TransferSpeedColumn::new()
    .style(Style::new().color(Color::parse("green").unwrap()));
```

Speed is calculated as `completed / elapsed_seconds`. Shows "0 B/s" at the start.

---

## Helper functions

### format_size

Format a byte count into a human-readable string using decimal (1000-based) units.

```rust
pub fn format_size(bytes: f64) -> String
```

```rust
use rusty_rich::progress_columns::format_size;

assert_eq!(format_size(0.0), "0 B");
assert_eq!(format_size(500.0), "500 B");
assert_eq!(format_size(1500.0), "1.5 KB");
assert_eq!(format_size(2_500_000.0), "2.5 MB");
```

### format_speed

Format a transfer speed (bytes per second) into a human-readable string.

```rust
pub fn format_speed(bytes_per_sec: f64) -> String
```

```rust
use rusty_rich::progress_columns::format_speed;

assert_eq!(format_speed(0.0), "0 B/s");
assert_eq!(format_speed(1500.0), "1.5 KB/s");
```

---

## Examples

### Basic single-task progress

```rust
use rusty_rich::{Progress, ProgressBar, Style, Color};

let mut progress = Progress::new();
let task_id = progress.add_task("Processing items", Some(50.0));

for i in 0..50 {
    // Do some work
    std::thread::sleep(std::time::Duration::from_millis(20));
    progress.advance(task_id, 1.0);

    // Render the current state
    print!("\r{}", progress.render(60));
    std::io::Write::flush(&mut std::io::stdout()).ok();
}
println!();
```

### Multi-task progress

```rust
use rusty_rich::Progress;

let mut progress = Progress::new();
let download = progress.add_task("Downloading", Some(100.0));
let install  = progress.add_task("Installing",  Some(50.0));
let verify   = progress.add_task("Verifying",   Some(20.0));

for step in 0..100 {
    progress.advance(download, 1.0);
    if step >= 50 {
        progress.advance(install, 1.0);
    }
    if step >= 80 {
        progress.advance(verify, 1.0);
    }
    print!("\r{}", progress.render(60));
    std::io::Write::flush(&mut std::io::stdout()).ok();
    std::thread::sleep(std::time::Duration::from_millis(15));
}
// Remove finished tasks
progress.remove_task(download);
println!("\nDone!");
```

### File download with progress

```rust
use rusty_rich::Progress;
use std::io::Read;

let mut progress = Progress::new();
let mut pf = progress
    .open("large-file.iso", "Downloading large-file.iso")
    .expect("Could not open file");

let mut buffer = [0u8; 4096];
loop {
    let n = pf.read(&mut buffer).expect("Read error");
    if n == 0 {
        break;
    }
    pf.sync(&mut progress);
    print!("\r{}", progress.render(70));
    std::io::Write::flush(&mut std::io::stdout()).ok();
    std::thread::sleep(std::time::Duration::from_millis(5));
}
println!();
```

### Custom columns

```rust
use rusty_rich::{
    Progress, Style, Color,
    progress_columns::*,
};

let mut progress = Progress::new();
progress.with_columns(vec![
    Box::new(SpinnerColumn::new()
        .style(Style::new().color(Color::parse("cyan").unwrap()))),
    Box::new(TextColumn::new("description")
        .format("{:>20}")
        .style(Style::new().bold(true))),
    Box::new(BarColumn::new()
        .width(40)
        .complete_style(Style::new().color(Color::parse("bright_green").unwrap()))),
    Box::new(TaskProgressColumn::new()
        .style(Style::new().color(Color::parse("yellow").unwrap()))),
    Box::new(TimeElapsedColumn::new()),
    Box::new(TimeRemainingColumn::new()),
    Box::new(DownloadColumn::new()
        .style(Style::new().color(Color::parse("blue").unwrap()))),
    Box::new(TransferSpeedColumn::new()
        .style(Style::new().color(Color::parse("green").unwrap()))),
]);

let task = progress.add_task("Custom download", Some(200.0));

// In a real application, update task.fields if TextColumn references fields:
if let Some(t) = progress.tasks.iter_mut().find(|t| t.id == task) {
    t.fields.insert("description".into(), "Custom download".into());
}

for i in 0..200 {
    progress.advance(task, 1.0);
    print!("\r{}", progress.render(100));
    std::io::Write::flush(&mut std::io::stdout()).ok();
    std::thread::sleep(std::time::Duration::from_millis(10));
}
println!();
```

### Using ProgressBar standalone

```rust
use rusty_rich::{ProgressBar, Style, Color};

let bar = ProgressBar::new()
    .total(1.0)
    .completed(0.67)
    .complete_style(Style::new().color(Color::parse("green").unwrap()))
    .remaining_style(Style::new().color(Color::parse("bright_black").unwrap()));

println!("Compiling... {}", bar.render(40));
```

---

## Module structure

- `rusty_rich::Progress` -- multi-task progress display
- `rusty_rich::Task` -- a single tracked task
- `rusty_rich::ProgressBar` -- standalone bar renderer
- `rusty_rich::TrackIterator` -- iterator with progress tracking
- `rusty_rich::ProgressFile` -- file read with progress tracking
- `rusty_rich::progress_columns::{BarColumn, SpinnerColumn, TextColumn, ...}` -- column types
- `rusty_rich::progress_columns::{format_size, format_speed}` -- formatting helpers

All items are re-exported from the crate root. The module files are:

- `/root/tuiproject/rust-rich/src/progress.rs` -- Progress, Task, ProgressBar, TrackIterator, ProgressFile
- `/root/tuiproject/rust-rich/src/progress_columns.rs` -- all column types and formatting helpers