inkling-loader 0.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,69 @@
1
+ Metadata-Version: 2.4
2
+ Name: inkling-loader
3
+ Version: 0.0.1
4
+ Classifier: Programming Language :: Rust
5
+ Classifier: Programming Language :: Python :: 3
6
+ Classifier: Topic :: Terminals
7
+ Classifier: Topic :: Utilities
8
+ Classifier: License :: OSI Approved :: MIT License
9
+ Summary: Reveal ASCII art as a progress indicator.
10
+ Keywords: progress,ascii,terminal,loader,tqdm
11
+ Author: Cody Taylor
12
+ License: MIT
13
+ Requires-Python: >=3.8
14
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
15
+ Project-URL: Homepage, https://github.com/codizzler/inkling
16
+ Project-URL: Repository, https://github.com/codizzler/inkling
17
+
18
+ # inkling (Python)
19
+
20
+ Reveal ASCII art as a progress indicator, from Python. Same engine as the Rust crate,
21
+ exposed as a tiny extension built with [PyO3](https://pyo3.rs). It installs as
22
+ `inkling-loader` and imports as `inkling`.
23
+
24
+ ```sh
25
+ pip install inkling-loader
26
+ ```
27
+
28
+ Determinate, like `tqdm` but the bar is a drawing:
29
+
30
+ ```python
31
+ from inkling import Loader
32
+
33
+ with Loader(total=len(items), rainbow=True) as bar:
34
+ for it in items:
35
+ work(it)
36
+ bar.inc()
37
+ ```
38
+
39
+ A download, setting the position as bytes arrive:
40
+
41
+ ```python
42
+ bar = Loader(total=content_length, art_path="dragon.txt")
43
+ for chunk in response:
44
+ file.write(chunk)
45
+ bar.inc(len(chunk))
46
+ bar.finish()
47
+ ```
48
+
49
+ | Method | Effect |
50
+ | --- | --- |
51
+ | `inc(delta=1)` | advance the position |
52
+ | `set(pos)` | set the absolute position |
53
+ | `set_length(total)` | change the total |
54
+ | `set_message(text)` | caption beneath the art |
55
+ | `finish()` / `finish_and_clear()` | finish, keeping or erasing the art |
56
+
57
+ Constructor keywords: `total`, `art`, `art_path`, `rainbow`, `geodesic`, `reading`,
58
+ `message`.
59
+
60
+ ## Building from source
61
+
62
+ ```sh
63
+ pip install maturin
64
+ maturin develop # build and install into the current venv
65
+ maturin build --release # produce a wheel in target/wheels
66
+ ```
67
+
68
+ Built from the [inkling](https://github.com/codizzler/inkling) Rust core. License: MIT.
69
+
@@ -0,0 +1,51 @@
1
+ # inkling (Python)
2
+
3
+ Reveal ASCII art as a progress indicator, from Python. Same engine as the Rust crate,
4
+ exposed as a tiny extension built with [PyO3](https://pyo3.rs). It installs as
5
+ `inkling-loader` and imports as `inkling`.
6
+
7
+ ```sh
8
+ pip install inkling-loader
9
+ ```
10
+
11
+ Determinate, like `tqdm` but the bar is a drawing:
12
+
13
+ ```python
14
+ from inkling import Loader
15
+
16
+ with Loader(total=len(items), rainbow=True) as bar:
17
+ for it in items:
18
+ work(it)
19
+ bar.inc()
20
+ ```
21
+
22
+ A download, setting the position as bytes arrive:
23
+
24
+ ```python
25
+ bar = Loader(total=content_length, art_path="dragon.txt")
26
+ for chunk in response:
27
+ file.write(chunk)
28
+ bar.inc(len(chunk))
29
+ bar.finish()
30
+ ```
31
+
32
+ | Method | Effect |
33
+ | --- | --- |
34
+ | `inc(delta=1)` | advance the position |
35
+ | `set(pos)` | set the absolute position |
36
+ | `set_length(total)` | change the total |
37
+ | `set_message(text)` | caption beneath the art |
38
+ | `finish()` / `finish_and_clear()` | finish, keeping or erasing the art |
39
+
40
+ Constructor keywords: `total`, `art`, `art_path`, `rainbow`, `geodesic`, `reading`,
41
+ `message`.
42
+
43
+ ## Building from source
44
+
45
+ ```sh
46
+ pip install maturin
47
+ maturin develop # build and install into the current venv
48
+ maturin build --release # produce a wheel in target/wheels
49
+ ```
50
+
51
+ Built from the [inkling](https://github.com/codizzler/inkling) Rust core. License: MIT.
@@ -0,0 +1,23 @@
1
+ [package]
2
+ name = "inkling-loader"
3
+ version = "0.0.1"
4
+ edition = "2021"
5
+ rust-version = "1.74"
6
+ description = "Reveal arbitrary ASCII art as a progress indicator by choosing the order its glyphs appear."
7
+ repository = "https://github.com/codizzler/inkling"
8
+ license = "MIT"
9
+ keywords = ["progress", "ascii", "terminal", "tui", "animation"]
10
+ categories = ["command-line-interface", "visualization"]
11
+
12
+ # Published as `inkling-loader` (the short name `inkling` is taken), but imported as `inkling`.
13
+ [lib]
14
+ name = "inkling"
15
+
16
+ [features]
17
+ default = ["terminal"]
18
+ # The core (art, rank, ordering, easing, frame) is pure std with zero
19
+ # dependencies. Only the live terminal renderer pulls in crossterm.
20
+ terminal = ["dep:crossterm"]
21
+
22
+ [dependencies]
23
+ crossterm = { version = "0.28", optional = true }
@@ -0,0 +1,43 @@
1
+ , ,
2
+ $, $, ,
3
+ "ss.$ss. .s'
4
+ , .ss$$$$$$$$$$s,
5
+ $. s$$$$$$$$$$$$$$`$$Ss
6
+ "$$$$$$$$$$$$$$$$$$o$$$ ,
7
+ s$$$$$$$$$$$$$$$$$$$$$$$$s, ,s
8
+ s$$$$$$$$$"$$$$$$""""$$$$$$"$$$$$,
9
+ s$$$$$$$$$$s""$$$$ssssss"$$$$$$$$"
10
+ s$$$$$$$$$$' `"""ss"$"$s""
11
+ s$$$$$$$$$$, `"""""$ .s$$s
12
+ s$$$$$$$$$$$$s,... `s$$' `
13
+ `ssss$$$$$$$$$$$$$$$$$$$$####s. .$$"$. , s-
14
+ `""""$$$$$$$$$$$$$$$$$$$$#####$$$$$$" $.$'
15
+ Posable artist: "$$$$$$$$$$$$$$$$$$$$$####s"" .$$$|
16
+ -Tua Xiong "$$$$$$$$$$$$$$$$$$$$$$$$##s .$$" $
17
+ $$""$$$$$$$$$$$$$$$$$$$$$$$$$$$$$" `
18
+ $$" "$"$$$$$$$$$$$$$$$$$$$$S""""'
19
+ , ," ' $$$$$$$$$$$$$$$$####s
20
+ $. .s$$$$$$$$$$$$$$$$$####"
21
+ , "$s. ..ssS$$$$$$$$$$$$$$$$$$$####"
22
+ $ .$$$S$$$$$$$$$$$$$$$$$$$$$$$$#####"
23
+ Ss ..sS$$$$$$$$$$$$$$$$$$$$$$$$$$$######""
24
+ "$$sS$$$$$$$$$$$$$$$$$$$$$$$$$$$########"
25
+ , s$$$$$$$$$$$$$$$$$$$$$$$$#########""'
26
+ $ s$$$$$$$$$$$$$$$$$$$$$#######""' s' ,
27
+ $$..$$$$$$$$$$$$$$$$$$######"' ....,$$.... ,$
28
+ "$$$$$$$$$$$$$$$######"' , .sS$$$$$$$$$$$$$$$$s$$
29
+ $$$$$$$$$$$$#####" $, .s$$$$$$$$$$$$$$$$$$$$$$$$s.
30
+ ) $$$$$$$$$$$#####' `$$$$$$$$$###########$$$$$$$$$$$.
31
+ (( $$$$$$$$$$$##### $$$$$$$$###" "####$$$$$$$$$$
32
+ ) \ $$$$$$$$$$$$####. $$$$$$###" "###$$$$$$$$$ s'
33
+ ( ) $$$$$$$$$$$$$####. $$$$$###" ####$$$$$$$$s$$'
34
+ ) ( ( $$"$$$$$$$$$$$#####.$$$$$###' .###$$$$$$$$$$"
35
+ ( ) ) _,$" $$$$$$$$$$$$######.$$##' .###$$$$$$$$$$
36
+ ) ( ( \. "$$$$$$$$$$$$$#######,,,. ..####$$$$$$$$$$$"
37
+ ( )$ ) ) ,$$$$$$$$$$$$$$$$$$####################$$$$$$$$$$$"
38
+ ( ($$ ( \ _sS" `"$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$S$$,
39
+ ) )$$$s ) ) . . `$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$"' `$$
40
+ ( $$$Ss/ .$, .$,,s$$$$$$##S$$$$$$$$$$$$$$$$$$$$$$$$S"" '
41
+ \)_$$$$$$$$$$$$$$$$$$$$$$$##" $$ `$$. `$$.
42
+ `"S$$$$$$$$$$$$$$$$$#" $ `$ `$
43
+ `"""""""""""""' ' ' '
@@ -0,0 +1,7 @@
1
+ ::::::::::: :::: ::: ::: ::: ::: ::::::::::: :::: ::: ::::::::
2
+ :+: :+:+: :+: :+: :+: :+: :+: :+:+: :+: :+: :+:
3
+ +:+ :+:+:+ +:+ +:+ +:+ +:+ +:+ :+:+:+ +:+ +:+
4
+ +#+ +#+ +:+ +#+ +#++:++ +#+ +#+ +#+ +:+ +#+ :#:
5
+ +#+ +#+ +#+#+# +#+ +#+ +#+ +#+ +#+ +#+#+# +#+ +#+#
6
+ #+# #+# #+#+# #+# #+# #+# #+# #+# #+#+# #+# #+#
7
+ ########### ### #### ### ### ########## ########### ### #### ########
@@ -0,0 +1,22 @@
1
+ _____ _____ _____ _____ _____ _____ _____
2
+ /\ \ /\ \ /\ \ /\ \ /\ \ /\ \ /\ \
3
+ /::\ \ /::\____\ /::\____\ /::\____\ /::\ \ /::\____\ /::\ \
4
+ \:::\ \ /::::| | /:::/ / /:::/ / \:::\ \ /::::| | /::::\ \
5
+ \:::\ \ /:::::| | /:::/ / /:::/ / \:::\ \ /:::::| | /::::::\ \
6
+ \:::\ \ /::::::| | /:::/ / /:::/ / \:::\ \ /::::::| | /:::/\:::\ \
7
+ \:::\ \ /:::/|::| | /:::/____/ /:::/ / \:::\ \ /:::/|::| | /:::/ \:::\ \
8
+ /::::\ \ /:::/ |::| | /::::\ \ /:::/ / /::::\ \ /:::/ |::| | /:::/ \:::\ \
9
+ ____ /::::::\ \ /:::/ |::| | _____ /::::::\____\________ /:::/ / ____ /::::::\ \ /:::/ |::| | _____ /:::/ / \:::\ \
10
+ /\ \ /:::/\:::\ \ /:::/ |::| |/\ \ /:::/\:::::::::::\ \ /:::/ / /\ \ /:::/\:::\ \ /:::/ |::| |/\ \ /:::/ / \:::\ ___\
11
+ /::\ \/:::/ \:::\____\/:: / |::| /::\____\/:::/ |:::::::::::\____\/:::/____/ /::\ \/:::/ \:::\____\/:: / |::| /::\____\/:::/____/ ___\:::| |
12
+ \:::\ /:::/ \::/ /\::/ /|::| /:::/ /\::/ |::|~~~|~~~~~ \:::\ \ \:::\ /:::/ \::/ /\::/ /|::| /:::/ /\:::\ \ /\ /:::|____|
13
+ \:::\/:::/ / \/____/ \/____/ |::| /:::/ / \/____|::| | \:::\ \ \:::\/:::/ / \/____/ \/____/ |::| /:::/ / \:::\ /::\ \::/ /
14
+ \::::::/ / |::|/:::/ / |::| | \:::\ \ \::::::/ / |::|/:::/ / \:::\ \:::\ \/____/
15
+ \::::/____/ |::::::/ / |::| | \:::\ \ \::::/____/ |::::::/ / \:::\ \:::\____\
16
+ \:::\ \ |:::::/ / |::| | \:::\ \ \:::\ \ |:::::/ / \:::\ /:::/ /
17
+ \:::\ \ |::::/ / |::| | \:::\ \ \:::\ \ |::::/ / \:::\/:::/ /
18
+ \:::\ \ /:::/ / |::| | \:::\ \ \:::\ \ /:::/ / \::::::/ /
19
+ \:::\____\ /:::/ / \::| | \:::\____\ \:::\____\ /:::/ / \::::/ /
20
+ \::/ / \::/ / \:| | \::/ / \::/ / \::/ / \::/____/
21
+ \/____/ \/____/ \|___| \/____/ \/____/ \/____/
22
+
@@ -0,0 +1,17 @@
1
+ /^\/^\
2
+ _|_O| O|
3
+ \/ /~ \_/ \
4
+ \____|__________/ \
5
+ \_______ \
6
+ `\ \ \
7
+ | | \
8
+ / / \
9
+ / / \\
10
+ / / \ \
11
+ / / \ \
12
+ / / _----_ \ \
13
+ / / _-~ ~-_ | |
14
+ ( ( _-~ _--_ ~-_ _/ |
15
+ \ ~-____-~ _-~ ~-_ ~-_-~ /
16
+ ~-_ _-~ ~-_ _-~
17
+ ~--______-~ ~-___-~
@@ -0,0 +1,138 @@
1
+ //! A demo that reads like a real CLI tool installing something, with the dragon
2
+ //! reveal as the download step. Paced like a true transfer and made for recording.
3
+ //!
4
+ //! cargo run --example download # glow, built-in dragon
5
+ //! cargo run --example download -- rainbow # rainbow palette
6
+ //! cargo run --example download -- geodesic serpent.txt # geodesic spine trace
7
+ //! cargo run --example download -- rainbow art.txt # rainbow, your own art
8
+
9
+ use std::io::{self, Write};
10
+ use std::thread;
11
+ use std::time::{Duration, Instant};
12
+
13
+ use crossterm::{
14
+ cursor::MoveTo,
15
+ execute,
16
+ style::{Color, Print, ResetColor, SetForegroundColor},
17
+ terminal::{Clear, ClearType},
18
+ };
19
+ use inkling::{ordering::Geodesic, render::Style, Loader};
20
+
21
+ fn step(mark: &str, color: Color, msg: &str) {
22
+ let _ = execute!(
23
+ io::stdout(),
24
+ Print(" "),
25
+ SetForegroundColor(color),
26
+ Print(mark),
27
+ ResetColor,
28
+ Print(format!(" {msg}\r\n"))
29
+ );
30
+ }
31
+
32
+ fn pause(ms: u64) {
33
+ thread::sleep(Duration::from_millis(ms));
34
+ }
35
+
36
+ fn main() {
37
+ let args: Vec<String> = std::env::args().skip(1).collect();
38
+ let rainbow = args.iter().any(|a| a == "rainbow");
39
+ let geodesic = args.iter().any(|a| a == "geodesic");
40
+ let art_path = args.iter().find(|a| a.ends_with(".txt")).cloned();
41
+ let started = Instant::now();
42
+ let gold = Color::Rgb {
43
+ r: 232,
44
+ g: 180,
45
+ b: 85,
46
+ };
47
+
48
+ // Clear to a fresh screen and print the tool banner.
49
+ let _ = execute!(
50
+ io::stdout(),
51
+ Clear(ClearType::All),
52
+ MoveTo(0, 0),
53
+ Print("\r\n "),
54
+ SetForegroundColor(gold),
55
+ Print("dragonctl"),
56
+ ResetColor,
57
+ Print(" v0.2.0\r\n\r\n")
58
+ );
59
+
60
+ step("\u{2713}", Color::Green, "Initializing demo environment");
61
+ pause(450);
62
+ step("\u{2713}", Color::Green, "Updating base image index");
63
+ pause(500);
64
+ step("\u{2193}", Color::Cyan, "Downloading base image");
65
+ let _ = execute!(io::stdout(), Print("\r\n"));
66
+
67
+ // The download itself, paced unevenly: a connect stall, a hiccup, a burst, a
68
+ // mid stall, and a slow tail.
69
+ let total: u64 = 48 * 1024 * 1024;
70
+ let mb = 1024.0 * 1024.0;
71
+ let schedule: &[(f64, u64)] = &[
72
+ (0.0, 600),
73
+ (5.0, 300),
74
+ (8.0, 250),
75
+ (8.0, 650),
76
+ (16.0, 220),
77
+ (27.0, 180),
78
+ (38.0, 200),
79
+ (45.0, 450),
80
+ (56.0, 200),
81
+ (67.0, 220),
82
+ (78.0, 260),
83
+ (87.0, 320),
84
+ (93.0, 480),
85
+ (97.0, 520),
86
+ (100.0, 300),
87
+ ];
88
+
89
+ let style = if rainbow {
90
+ Style::rainbow()
91
+ } else {
92
+ Style::default()
93
+ };
94
+ let mut builder = Loader::builder().total(total).style(style);
95
+ if geodesic {
96
+ builder = builder.ordering(Geodesic::default());
97
+ }
98
+ if let Some(path) = &art_path {
99
+ let text = std::fs::read_to_string(path).unwrap_or_else(|e| {
100
+ eprintln!("could not read {path}: {e}");
101
+ std::process::exit(1);
102
+ });
103
+ builder = builder.art(inkling::Art::parse(&text));
104
+ }
105
+ let loader = builder.start();
106
+ for &(percent, dwell) in schedule {
107
+ let done = (percent / 100.0 * total as f64) as u64;
108
+ loader.set(done);
109
+ loader.set_message(format!(
110
+ "dragon.iso {:.1} / {:.1} MB",
111
+ done as f64 / mb,
112
+ total as f64 / mb
113
+ ));
114
+ pause(dwell);
115
+ }
116
+ loader.finish();
117
+
118
+ // A finish that ties it together.
119
+ let _ = execute!(io::stdout(), Print("\r\n"));
120
+ step(
121
+ "\u{2713}",
122
+ Color::Green,
123
+ "Verified checksum sha256:a1b2c3d4e5",
124
+ );
125
+ pause(400);
126
+ let _ = execute!(
127
+ io::stdout(),
128
+ Print("\r\n "),
129
+ SetForegroundColor(Color::Green),
130
+ Print("Done"),
131
+ ResetColor,
132
+ Print(format!(
133
+ " in {:.1}s. dragon.iso (48.0 MB) ready.\r\n\r\n",
134
+ started.elapsed().as_secs_f64()
135
+ ))
136
+ );
137
+ let _ = io::stdout().flush();
138
+ }
@@ -0,0 +1,152 @@
1
+ //! The dragon demo.
2
+ //!
3
+ //! cargo run --example dragon # live reveal in your terminal
4
+ //! cargo run --example dragon -- --snapshots # staged text frames (no TTY needed)
5
+ //! cargo run --example dragon -- --art path/to.txt # bring your own ASCII art
6
+ //!
7
+ //! By default it reveals a procedurally generated serpent, a single, perfectly
8
+ //! 8-connected stroke, so the geodesic spine-trace paints it tip-to-tip. Point
9
+ //! `--art` at any file to watch arbitrary art reveal (imperfect art leans on the
10
+ //! island fallback and still looks intentional).
11
+
12
+ use std::io::IsTerminal;
13
+
14
+ use inkling::{
15
+ art::Art,
16
+ frame,
17
+ ordering::{Geodesic, GeodesicReport, Ordering, StartHint},
18
+ };
19
+
20
+ fn main() {
21
+ let args: Vec<String> = std::env::args().skip(1).collect();
22
+ let snapshots = args.iter().any(|a| a == "--snapshots");
23
+
24
+ let art = match arg_value(&args, "--art") {
25
+ Some(path) => match std::fs::read_to_string(&path) {
26
+ Ok(text) => Art::parse(&text),
27
+ Err(e) => {
28
+ eprintln!("inkling: could not read {path}: {e}");
29
+ std::process::exit(1);
30
+ }
31
+ },
32
+ None => Art::parse(&serpent(64, 13)),
33
+ };
34
+
35
+ let ordering = Geodesic {
36
+ start: StartHint::TopLeft,
37
+ };
38
+ let GeodesicReport {
39
+ ink_cells,
40
+ connected_cells,
41
+ spine_length,
42
+ } = ordering.diagnose(&art);
43
+ let ranks = ordering.rank(&art);
44
+
45
+ eprintln!(
46
+ "inkling · {ink_cells} ink cells · {connected_cells} on the spine \
47
+ ({:.0}% connected) · spine length {spine_length}",
48
+ 100.0 * connected_cells as f32 / ink_cells.max(1) as f32,
49
+ );
50
+
51
+ // Headless / piped / explicit: print staged text frames and exit.
52
+ if snapshots || !std::io::stdout().is_terminal() {
53
+ for p in [0.0, 0.2, 0.4, 0.6, 0.8, 1.0] {
54
+ println!("\n── progress {:>3.0}% {}", p * 100.0, "─".repeat(28));
55
+ print!("{}", frame::to_string(&art, &ranks, p));
56
+ }
57
+ return;
58
+ }
59
+
60
+ #[cfg(feature = "terminal")]
61
+ {
62
+ use inkling::{
63
+ easing::Easing,
64
+ render::{animate, Style},
65
+ };
66
+ use std::time::Duration;
67
+ if let Err(e) = animate(
68
+ &art,
69
+ &ranks,
70
+ Style::default(),
71
+ Duration::from_millis(3500),
72
+ Easing::EaseInOutCubic,
73
+ ) {
74
+ eprintln!("inkling: render error: {e}");
75
+ }
76
+ }
77
+ }
78
+
79
+ /// Read the value following `key` in `args` (supports `--key value` and `--key=value`).
80
+ fn arg_value(args: &[String], key: &str) -> Option<String> {
81
+ let mut it = args.iter();
82
+ while let Some(a) = it.next() {
83
+ if a == key {
84
+ return it.next().cloned();
85
+ }
86
+ if let Some(v) = a.strip_prefix(&format!("{key}=")) {
87
+ return Some(v.to_string());
88
+ }
89
+ }
90
+ None
91
+ }
92
+
93
+ /// Generate an Eastern-style serpent as a single 8-connected stroke: a sine-wave
94
+ /// body with crest spikes and a small head. Connectivity is guaranteed by
95
+ /// filling any vertical gap between adjacent columns, so the spine-trace reveals
96
+ /// it flawlessly from tail to head.
97
+ // Columns are scanned by index because each row `y` is *computed* from `x`
98
+ // (y = sine(x)); writing `grid[y][x]` is the clearest expression of that.
99
+ #[allow(clippy::needless_range_loop)]
100
+ fn serpent(width: usize, height: usize) -> String {
101
+ use std::f32::consts::TAU;
102
+
103
+ let amp = ((height as f32) - 3.0).max(1.0) / 2.0;
104
+ let mid = (height as f32 - 1.0) / 2.0;
105
+ let period = (width as f32) / 2.0; // two coils across the width
106
+
107
+ let y_at = |x: usize| -> usize {
108
+ let yf = mid - amp * ((x as f32) / period * TAU).sin();
109
+ yf.round().clamp(0.0, height as f32 - 1.0) as usize
110
+ };
111
+
112
+ let mut grid = vec![vec![' '; width]; height];
113
+ let mut prev_y = y_at(0);
114
+ for x in 0..width {
115
+ let y = y_at(x);
116
+ // Bridge any vertical gap so the body is one continuous stroke.
117
+ if x > 0 && y.abs_diff(prev_y) > 1 {
118
+ for row in grid[y.min(prev_y)..=y.max(prev_y)].iter_mut() {
119
+ if row[x] == ' ' {
120
+ row[x] = '|';
121
+ }
122
+ }
123
+ }
124
+ grid[y][x] = match prev_y {
125
+ _ if x == 0 => '~',
126
+ py if y < py => '/',
127
+ py if y > py => '\\',
128
+ _ => '~',
129
+ };
130
+ prev_y = y;
131
+ }
132
+
133
+ // Spikes on the crests (local highs), one row above the body.
134
+ for x in 1..width.saturating_sub(1) {
135
+ let y = y_at(x);
136
+ if y > 0 && y < y_at(x - 1) && y <= y_at(x + 1) {
137
+ grid[y - 1][x] = '^';
138
+ }
139
+ }
140
+
141
+ // A small head at the leading (right) tip.
142
+ let (hx, hy) = (width - 1, y_at(width - 1));
143
+ grid[hy][hx] = '>';
144
+ if hy > 0 {
145
+ grid[hy - 1][hx.saturating_sub(1)] = 'o'; // eye
146
+ }
147
+
148
+ grid.into_iter()
149
+ .map(|row| row.into_iter().collect::<String>())
150
+ .collect::<Vec<_>>()
151
+ .join("\n")
152
+ }
@@ -0,0 +1,73 @@
1
+ //! A tour of Inkling as a loader.
2
+ //!
3
+ //! cargo run --example loader # determinate dragon loader
4
+ //! cargo run --example loader -- iter # wrap an iterator
5
+ //! cargo run --example loader -- spinner # indeterminate spinner
6
+ //! cargo run --example loader -- threads # progress from worker threads
7
+ //! cargo run --example loader -- rainbow # lolcat-style rainbow palette
8
+ //!
9
+ //! On a real terminal the dragon paints itself as the work runs. Piped or in CI it
10
+ //! prints the finished art once instead.
11
+
12
+ use std::thread;
13
+ use std::time::Duration;
14
+
15
+ use inkling::{Loader, ProgressIteratorExt};
16
+
17
+ fn work(ms: u64) {
18
+ thread::sleep(Duration::from_millis(ms));
19
+ }
20
+
21
+ fn main() {
22
+ match std::env::args().nth(1).as_deref() {
23
+ Some("iter") => {
24
+ for _ in (0..100).inkling() {
25
+ work(20);
26
+ }
27
+ }
28
+ Some("spinner") => {
29
+ let loader = Loader::spinner();
30
+ loader.set_message("Doing something mysterious");
31
+ work(2500);
32
+ loader.finish();
33
+ }
34
+ Some("threads") => {
35
+ let loader = Loader::new(120);
36
+ loader.set_message("Four workers, one dragon");
37
+ thread::scope(|s| {
38
+ for _ in 0..4 {
39
+ let handle = loader.handle();
40
+ s.spawn(move || {
41
+ for _ in 0..30 {
42
+ work(30);
43
+ handle.inc(1);
44
+ }
45
+ });
46
+ }
47
+ });
48
+ loader.finish();
49
+ }
50
+ Some("rainbow") => {
51
+ let loader = Loader::builder()
52
+ .total(100)
53
+ .style(inkling::render::Style::rainbow())
54
+ .message("Tasting the rainbow")
55
+ .start();
56
+ for _ in 0..100 {
57
+ work(20);
58
+ loader.inc(1);
59
+ }
60
+ loader.finish();
61
+ }
62
+ _ => {
63
+ let total: u64 = 100;
64
+ let loader = Loader::new(total);
65
+ loader.set_message("Summoning the dragon");
66
+ for _ in 0..total {
67
+ work(20);
68
+ loader.inc(1);
69
+ }
70
+ loader.finish();
71
+ }
72
+ }
73
+ }