Skip to main content

Progress

Struct Progress 

Source
pub struct Progress { /* private fields */ }
Expand description

Tracks download progress and prints styled result lines.

Implementations§

Source§

impl Progress

Source

pub fn new(total: usize) -> Self

Create a new progress tracker for total items.

Examples found in repository?
examples/mytool.rs (line 141)
82fn main() {
83    // parse() handles --help, --version, --completions,
84    // unknown flags, and missing positionals automatically
85    let opt = Opt::parse();
86
87    // resolved_alt_input() generated from conflicts_with = "input"
88    let input = opt.resolved_alt_input();
89    let output = opt.resolved_alt_output();
90    let start = std::time::Instant::now();
91
92    // bail! prints error and exits 1
93    if !input.exists() {
94        bail!("input path does not exist: {}", input.display());
95    }
96
97    header!(
98        "mytool",
99        env!("CARGO_PKG_VERSION"),
100        "batch file processor",
101        "fast and parallel"
102    );
103
104    phase!("init", "scanning {}", input.display());
105    phase!(
106        "scan",
107        "found {} remote · {} local   jobs: {}",
108        8,
109        3,
110        opt.jobs
111    );
112
113    if opt.dry_run {
114        phase!("mode", "dry run — nothing will be written");
115    }
116    if !opt.include.is_empty() {
117        phase!("filter", "include: {}", opt.include.join(", "));
118    }
119    if !opt.exclude.is_empty() {
120        phase!("filter", "exclude: {}", opt.exclude.join(", "));
121    }
122
123    // ── remote files ──────────────────────────────────────────────────
124    let remote: &[(&str, &str, usize)] = &[
125        ("https://example.com/data.csv", "./data/data.csv", 43_100),
126        ("https://example.com/ref.json", "./data/ref.json", 8_500),
127        (
128            "https://example.com/schema.json",
129            "./data/schema.json",
130            2_300,
131        ),
132        ("https://cdn.example.com/lib.js", "./js/lib.js", 128_000),
133        (
134            "https://cdn.example.com/style.css",
135            "./css/style.css",
136            12_400,
137        ),
138    ];
139
140    step!("downloading remote files");
141    let pb = Progress::new(remote.len());
142    let mut errors = 0usize;
143    for (url, local, bytes) in remote {
144        // simulate one failure
145        if url.contains("lib.js") {
146            pb.fail("file", url, "connection timeout");
147            errors += 1;
148        } else {
149            pb.ok("file", "remote", url, local, *bytes);
150        }
151    }
152    pb.finish();
153
154    // ── local files ───────────────────────────────────────────────────
155    let local: &[(&str, &str, usize)] = &[
156        ("config.toml", "./cfg/config.toml", 1_200),
157        ("rules.yaml", "./cfg/rules.yaml", 3_400),
158        ("README.md", "./README.md", 8_800),
159    ];
160
161    step!("copying local files");
162    let pb2 = Progress::new(local.len());
163    for (src, dst, bytes) in local {
164        pb2.ok("file", "local", src, dst, *bytes);
165    }
166    pb2.finish();
167
168    // ── nested operations ─────────────────────────────────────────────
169    step!("resolving references in style.css");
170    substep!(
171        "https://fonts.example.com/Inter.woff2",
172        "./fonts/Inter.woff2"
173    );
174    substep!(
175        "https://fonts.example.com/JetBrains.woff2",
176        "./fonts/JetBrains.woff2"
177    );
178
179    // ── write ─────────────────────────────────────────────────────────
180    if !opt.dry_run {
181        step!("writing output");
182        ok!(output.join(format!("result.{}", opt.format)).display());
183    }
184
185    // ── summary ───────────────────────────────────────────────────────
186    let remote_ok = remote.len() - errors;
187    let total_files = remote_ok + local.len();
188    let total_bytes: usize = remote
189        .iter()
190        .filter(|(u, _, _)| !u.contains("lib.js"))
191        .map(|(_, _, b)| b)
192        .chain(local.iter().map(|(_, _, b)| b))
193        .sum();
194
195    let input_val = paint(CYAN, &input.display().to_string());
196    let output_val = paint(
197        BOLD,
198        &output
199            .join(format!("result.{}", opt.format))
200            .display()
201            .to_string(),
202    );
203    let files_val = format!(
204        "{} total · {} errors",
205        paint(OK, &total_files.to_string()),
206        paint(if errors > 0 { ERR } else { DIM }, &errors.to_string())
207    );
208    let size_val = paint(YELLOW, &format_bytes(total_bytes));
209    let time_val = paint(DIM, &format!("{}ms", start.elapsed().as_millis()));
210
211    if errors == 0 {
212        summary! {
213            done:    "All files processed",
214            "input"  => input_val,
215            "output" => output_val,
216            section,
217            "files"  => files_val,
218            "size"   => size_val,
219            "time"   => time_val,
220        }
221    } else {
222        summary! {
223            warn:    "Completed with errors",
224            "input"  => input_val,
225            "output" => output_val,
226            section,
227            "files"  => files_val,
228            "size"   => size_val,
229            "time"   => time_val,
230        }
231    }
232}
Source

pub fn ok(&self, kind: &str, source: &str, url: &str, local: &str, bytes: usize)

Print a successful download line.

  ✓  [style ][remote]  https://…  →  ./css/file.css  [1.37 KB]
Examples found in repository?
examples/mytool.rs (line 149)
82fn main() {
83    // parse() handles --help, --version, --completions,
84    // unknown flags, and missing positionals automatically
85    let opt = Opt::parse();
86
87    // resolved_alt_input() generated from conflicts_with = "input"
88    let input = opt.resolved_alt_input();
89    let output = opt.resolved_alt_output();
90    let start = std::time::Instant::now();
91
92    // bail! prints error and exits 1
93    if !input.exists() {
94        bail!("input path does not exist: {}", input.display());
95    }
96
97    header!(
98        "mytool",
99        env!("CARGO_PKG_VERSION"),
100        "batch file processor",
101        "fast and parallel"
102    );
103
104    phase!("init", "scanning {}", input.display());
105    phase!(
106        "scan",
107        "found {} remote · {} local   jobs: {}",
108        8,
109        3,
110        opt.jobs
111    );
112
113    if opt.dry_run {
114        phase!("mode", "dry run — nothing will be written");
115    }
116    if !opt.include.is_empty() {
117        phase!("filter", "include: {}", opt.include.join(", "));
118    }
119    if !opt.exclude.is_empty() {
120        phase!("filter", "exclude: {}", opt.exclude.join(", "));
121    }
122
123    // ── remote files ──────────────────────────────────────────────────
124    let remote: &[(&str, &str, usize)] = &[
125        ("https://example.com/data.csv", "./data/data.csv", 43_100),
126        ("https://example.com/ref.json", "./data/ref.json", 8_500),
127        (
128            "https://example.com/schema.json",
129            "./data/schema.json",
130            2_300,
131        ),
132        ("https://cdn.example.com/lib.js", "./js/lib.js", 128_000),
133        (
134            "https://cdn.example.com/style.css",
135            "./css/style.css",
136            12_400,
137        ),
138    ];
139
140    step!("downloading remote files");
141    let pb = Progress::new(remote.len());
142    let mut errors = 0usize;
143    for (url, local, bytes) in remote {
144        // simulate one failure
145        if url.contains("lib.js") {
146            pb.fail("file", url, "connection timeout");
147            errors += 1;
148        } else {
149            pb.ok("file", "remote", url, local, *bytes);
150        }
151    }
152    pb.finish();
153
154    // ── local files ───────────────────────────────────────────────────
155    let local: &[(&str, &str, usize)] = &[
156        ("config.toml", "./cfg/config.toml", 1_200),
157        ("rules.yaml", "./cfg/rules.yaml", 3_400),
158        ("README.md", "./README.md", 8_800),
159    ];
160
161    step!("copying local files");
162    let pb2 = Progress::new(local.len());
163    for (src, dst, bytes) in local {
164        pb2.ok("file", "local", src, dst, *bytes);
165    }
166    pb2.finish();
167
168    // ── nested operations ─────────────────────────────────────────────
169    step!("resolving references in style.css");
170    substep!(
171        "https://fonts.example.com/Inter.woff2",
172        "./fonts/Inter.woff2"
173    );
174    substep!(
175        "https://fonts.example.com/JetBrains.woff2",
176        "./fonts/JetBrains.woff2"
177    );
178
179    // ── write ─────────────────────────────────────────────────────────
180    if !opt.dry_run {
181        step!("writing output");
182        ok!(output.join(format!("result.{}", opt.format)).display());
183    }
184
185    // ── summary ───────────────────────────────────────────────────────
186    let remote_ok = remote.len() - errors;
187    let total_files = remote_ok + local.len();
188    let total_bytes: usize = remote
189        .iter()
190        .filter(|(u, _, _)| !u.contains("lib.js"))
191        .map(|(_, _, b)| b)
192        .chain(local.iter().map(|(_, _, b)| b))
193        .sum();
194
195    let input_val = paint(CYAN, &input.display().to_string());
196    let output_val = paint(
197        BOLD,
198        &output
199            .join(format!("result.{}", opt.format))
200            .display()
201            .to_string(),
202    );
203    let files_val = format!(
204        "{} total · {} errors",
205        paint(OK, &total_files.to_string()),
206        paint(if errors > 0 { ERR } else { DIM }, &errors.to_string())
207    );
208    let size_val = paint(YELLOW, &format_bytes(total_bytes));
209    let time_val = paint(DIM, &format!("{}ms", start.elapsed().as_millis()));
210
211    if errors == 0 {
212        summary! {
213            done:    "All files processed",
214            "input"  => input_val,
215            "output" => output_val,
216            section,
217            "files"  => files_val,
218            "size"   => size_val,
219            "time"   => time_val,
220        }
221    } else {
222        summary! {
223            warn:    "Completed with errors",
224            "input"  => input_val,
225            "output" => output_val,
226            section,
227            "files"  => files_val,
228            "size"   => size_val,
229            "time"   => time_val,
230        }
231    }
232}
Source

pub fn fail(&self, kind: &str, url: &str, error: &str)

Print a failed download line, aligned with ok() lines.

  (4/5)  [file  ][error ]  https://…  (connection timeout)
Examples found in repository?
examples/mytool.rs (line 146)
82fn main() {
83    // parse() handles --help, --version, --completions,
84    // unknown flags, and missing positionals automatically
85    let opt = Opt::parse();
86
87    // resolved_alt_input() generated from conflicts_with = "input"
88    let input = opt.resolved_alt_input();
89    let output = opt.resolved_alt_output();
90    let start = std::time::Instant::now();
91
92    // bail! prints error and exits 1
93    if !input.exists() {
94        bail!("input path does not exist: {}", input.display());
95    }
96
97    header!(
98        "mytool",
99        env!("CARGO_PKG_VERSION"),
100        "batch file processor",
101        "fast and parallel"
102    );
103
104    phase!("init", "scanning {}", input.display());
105    phase!(
106        "scan",
107        "found {} remote · {} local   jobs: {}",
108        8,
109        3,
110        opt.jobs
111    );
112
113    if opt.dry_run {
114        phase!("mode", "dry run — nothing will be written");
115    }
116    if !opt.include.is_empty() {
117        phase!("filter", "include: {}", opt.include.join(", "));
118    }
119    if !opt.exclude.is_empty() {
120        phase!("filter", "exclude: {}", opt.exclude.join(", "));
121    }
122
123    // ── remote files ──────────────────────────────────────────────────
124    let remote: &[(&str, &str, usize)] = &[
125        ("https://example.com/data.csv", "./data/data.csv", 43_100),
126        ("https://example.com/ref.json", "./data/ref.json", 8_500),
127        (
128            "https://example.com/schema.json",
129            "./data/schema.json",
130            2_300,
131        ),
132        ("https://cdn.example.com/lib.js", "./js/lib.js", 128_000),
133        (
134            "https://cdn.example.com/style.css",
135            "./css/style.css",
136            12_400,
137        ),
138    ];
139
140    step!("downloading remote files");
141    let pb = Progress::new(remote.len());
142    let mut errors = 0usize;
143    for (url, local, bytes) in remote {
144        // simulate one failure
145        if url.contains("lib.js") {
146            pb.fail("file", url, "connection timeout");
147            errors += 1;
148        } else {
149            pb.ok("file", "remote", url, local, *bytes);
150        }
151    }
152    pb.finish();
153
154    // ── local files ───────────────────────────────────────────────────
155    let local: &[(&str, &str, usize)] = &[
156        ("config.toml", "./cfg/config.toml", 1_200),
157        ("rules.yaml", "./cfg/rules.yaml", 3_400),
158        ("README.md", "./README.md", 8_800),
159    ];
160
161    step!("copying local files");
162    let pb2 = Progress::new(local.len());
163    for (src, dst, bytes) in local {
164        pb2.ok("file", "local", src, dst, *bytes);
165    }
166    pb2.finish();
167
168    // ── nested operations ─────────────────────────────────────────────
169    step!("resolving references in style.css");
170    substep!(
171        "https://fonts.example.com/Inter.woff2",
172        "./fonts/Inter.woff2"
173    );
174    substep!(
175        "https://fonts.example.com/JetBrains.woff2",
176        "./fonts/JetBrains.woff2"
177    );
178
179    // ── write ─────────────────────────────────────────────────────────
180    if !opt.dry_run {
181        step!("writing output");
182        ok!(output.join(format!("result.{}", opt.format)).display());
183    }
184
185    // ── summary ───────────────────────────────────────────────────────
186    let remote_ok = remote.len() - errors;
187    let total_files = remote_ok + local.len();
188    let total_bytes: usize = remote
189        .iter()
190        .filter(|(u, _, _)| !u.contains("lib.js"))
191        .map(|(_, _, b)| b)
192        .chain(local.iter().map(|(_, _, b)| b))
193        .sum();
194
195    let input_val = paint(CYAN, &input.display().to_string());
196    let output_val = paint(
197        BOLD,
198        &output
199            .join(format!("result.{}", opt.format))
200            .display()
201            .to_string(),
202    );
203    let files_val = format!(
204        "{} total · {} errors",
205        paint(OK, &total_files.to_string()),
206        paint(if errors > 0 { ERR } else { DIM }, &errors.to_string())
207    );
208    let size_val = paint(YELLOW, &format_bytes(total_bytes));
209    let time_val = paint(DIM, &format!("{}ms", start.elapsed().as_millis()));
210
211    if errors == 0 {
212        summary! {
213            done:    "All files processed",
214            "input"  => input_val,
215            "output" => output_val,
216            section,
217            "files"  => files_val,
218            "size"   => size_val,
219            "time"   => time_val,
220        }
221    } else {
222        summary! {
223            warn:    "Completed with errors",
224            "input"  => input_val,
225            "output" => output_val,
226            section,
227            "files"  => files_val,
228            "size"   => size_val,
229            "time"   => time_val,
230        }
231    }
232}
Source

pub fn finish(&self)

Print a blank line after the download block.

Examples found in repository?
examples/mytool.rs (line 152)
82fn main() {
83    // parse() handles --help, --version, --completions,
84    // unknown flags, and missing positionals automatically
85    let opt = Opt::parse();
86
87    // resolved_alt_input() generated from conflicts_with = "input"
88    let input = opt.resolved_alt_input();
89    let output = opt.resolved_alt_output();
90    let start = std::time::Instant::now();
91
92    // bail! prints error and exits 1
93    if !input.exists() {
94        bail!("input path does not exist: {}", input.display());
95    }
96
97    header!(
98        "mytool",
99        env!("CARGO_PKG_VERSION"),
100        "batch file processor",
101        "fast and parallel"
102    );
103
104    phase!("init", "scanning {}", input.display());
105    phase!(
106        "scan",
107        "found {} remote · {} local   jobs: {}",
108        8,
109        3,
110        opt.jobs
111    );
112
113    if opt.dry_run {
114        phase!("mode", "dry run — nothing will be written");
115    }
116    if !opt.include.is_empty() {
117        phase!("filter", "include: {}", opt.include.join(", "));
118    }
119    if !opt.exclude.is_empty() {
120        phase!("filter", "exclude: {}", opt.exclude.join(", "));
121    }
122
123    // ── remote files ──────────────────────────────────────────────────
124    let remote: &[(&str, &str, usize)] = &[
125        ("https://example.com/data.csv", "./data/data.csv", 43_100),
126        ("https://example.com/ref.json", "./data/ref.json", 8_500),
127        (
128            "https://example.com/schema.json",
129            "./data/schema.json",
130            2_300,
131        ),
132        ("https://cdn.example.com/lib.js", "./js/lib.js", 128_000),
133        (
134            "https://cdn.example.com/style.css",
135            "./css/style.css",
136            12_400,
137        ),
138    ];
139
140    step!("downloading remote files");
141    let pb = Progress::new(remote.len());
142    let mut errors = 0usize;
143    for (url, local, bytes) in remote {
144        // simulate one failure
145        if url.contains("lib.js") {
146            pb.fail("file", url, "connection timeout");
147            errors += 1;
148        } else {
149            pb.ok("file", "remote", url, local, *bytes);
150        }
151    }
152    pb.finish();
153
154    // ── local files ───────────────────────────────────────────────────
155    let local: &[(&str, &str, usize)] = &[
156        ("config.toml", "./cfg/config.toml", 1_200),
157        ("rules.yaml", "./cfg/rules.yaml", 3_400),
158        ("README.md", "./README.md", 8_800),
159    ];
160
161    step!("copying local files");
162    let pb2 = Progress::new(local.len());
163    for (src, dst, bytes) in local {
164        pb2.ok("file", "local", src, dst, *bytes);
165    }
166    pb2.finish();
167
168    // ── nested operations ─────────────────────────────────────────────
169    step!("resolving references in style.css");
170    substep!(
171        "https://fonts.example.com/Inter.woff2",
172        "./fonts/Inter.woff2"
173    );
174    substep!(
175        "https://fonts.example.com/JetBrains.woff2",
176        "./fonts/JetBrains.woff2"
177    );
178
179    // ── write ─────────────────────────────────────────────────────────
180    if !opt.dry_run {
181        step!("writing output");
182        ok!(output.join(format!("result.{}", opt.format)).display());
183    }
184
185    // ── summary ───────────────────────────────────────────────────────
186    let remote_ok = remote.len() - errors;
187    let total_files = remote_ok + local.len();
188    let total_bytes: usize = remote
189        .iter()
190        .filter(|(u, _, _)| !u.contains("lib.js"))
191        .map(|(_, _, b)| b)
192        .chain(local.iter().map(|(_, _, b)| b))
193        .sum();
194
195    let input_val = paint(CYAN, &input.display().to_string());
196    let output_val = paint(
197        BOLD,
198        &output
199            .join(format!("result.{}", opt.format))
200            .display()
201            .to_string(),
202    );
203    let files_val = format!(
204        "{} total · {} errors",
205        paint(OK, &total_files.to_string()),
206        paint(if errors > 0 { ERR } else { DIM }, &errors.to_string())
207    );
208    let size_val = paint(YELLOW, &format_bytes(total_bytes));
209    let time_val = paint(DIM, &format!("{}ms", start.elapsed().as_millis()));
210
211    if errors == 0 {
212        summary! {
213            done:    "All files processed",
214            "input"  => input_val,
215            "output" => output_val,
216            section,
217            "files"  => files_val,
218            "size"   => size_val,
219            "time"   => time_val,
220        }
221    } else {
222        summary! {
223            warn:    "Completed with errors",
224            "input"  => input_val,
225            "output" => output_val,
226            section,
227            "files"  => files_val,
228            "size"   => size_val,
229            "time"   => time_val,
230        }
231    }
232}

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.