]>
git.proxmox.com Git - cargo.git/blob - src/cargo/util/progress.rs
fe90d311914e4245ece7f10b9a56596522c1dd49
3 use std
::time
::{Duration, Instant}
;
5 use core
::shell
::Verbosity
;
6 use util
::{CargoResult, Config}
;
8 use unicode_width
::UnicodeWidthChar
;
10 pub struct Progress
<'cfg
> {
11 state
: Option
<State
<'cfg
>>,
14 pub enum ProgressStyle
{
38 impl<'cfg
> Progress
<'cfg
> {
39 pub fn with_style(name
: &str, style
: ProgressStyle
, cfg
: &'cfg Config
) -> Progress
<'cfg
> {
40 // report no progress when -q (for quiet) or TERM=dumb are set
41 // or if running on Continuous Integration service like Travis where the
42 // output logs get mangled.
43 let dumb
= match env
::var("TERM") {
44 Ok(term
) => term
== "dumb",
47 if cfg
.shell().verbosity() == Verbosity
::Quiet
|| dumb
|| env
::var("CI").is_ok() {
48 return Progress { state: None }
;
52 state
: cfg
.shell().err_width().map(|n
| State
{
59 name
: name
.to_string(),
61 throttle
: Throttle
::new(),
66 pub fn disable(&mut self) {
70 pub fn is_enabled(&self) -> bool
{
74 pub fn new(name
: &str, cfg
: &'cfg Config
) -> Progress
<'cfg
> {
75 Self::with_style(name
, ProgressStyle
::Percentage
, cfg
)
78 pub fn tick(&mut self, cur
: usize, max
: usize) -> CargoResult
<()> {
79 let s
= match &mut self.state
{
81 None
=> return Ok(()),
84 // Don't update too often as it can cause excessive performance loss
85 // just putting stuff onto the terminal. We also want to avoid
86 // flickering by not drawing anything that goes away too quickly. As a
87 // result we've got two branches here:
89 // 1. If we haven't drawn anything, we wait for a period of time to
90 // actually start drawing to the console. This ensures that
91 // short-lived operations don't flicker on the console. Currently
92 // there's a 500ms delay to when we first draw something.
93 // 2. If we've drawn something, then we rate limit ourselves to only
94 // draw to the console every so often. Currently there's a 100ms
95 // delay between updates.
96 if !s
.throttle
.allowed() {
103 pub fn tick_now(&mut self, cur
: usize, max
: usize, msg
: &str) -> CargoResult
<()> {
105 Some(ref mut s
) => s
.tick(cur
, max
, msg
),
110 pub fn update_allowed(&mut self) -> bool
{
111 match &mut self.state
{
112 Some(s
) => s
.throttle
.allowed(),
117 pub fn print_now(&mut self, msg
: &str) -> CargoResult
<()> {
118 match &mut self.state
{
119 Some(s
) => s
.print("", msg
),
124 pub fn clear(&mut self) {
125 if let Some(ref mut s
) = self.state
{
132 fn new() -> Throttle
{
135 last_update
: Instant
::now(),
139 fn allowed(&mut self) -> bool
{
141 let delay
= Duration
::from_millis(500);
142 if self.last_update
.elapsed() < delay
{
146 let interval
= Duration
::from_millis(100);
147 if self.last_update
.elapsed() < interval
{
155 fn update(&mut self) {
157 self.last_update
= Instant
::now();
161 impl<'cfg
> State
<'cfg
> {
162 fn tick(&mut self, cur
: usize, max
: usize, msg
: &str) -> CargoResult
<()> {
167 if max
> 0 && cur
== max
{
171 // Write out a pretty header, then the progress bar itself, and then
172 // return back to the beginning of the line for the next print.
173 self.try_update_max_width();
174 if let Some(pbar
) = self.format
.progress(cur
, max
) {
175 self.print(&pbar
, msg
)?
;
180 fn print(&mut self, prefix
: &str, msg
: &str) -> CargoResult
<()> {
181 self.throttle
.update();
182 self.try_update_max_width();
184 // make sure we have enough room for the header
185 if self.format
.max_width
< 15 {
188 self.config
.shell().status_header(&self.name
)?
;
189 let mut line
= prefix
.to_string();
190 self.format
.render(&mut line
, msg
);
192 while line
.len() < self.format
.max_width
- 15 {
196 write
!(self.config
.shell().err(), "{}\r", line
)?
;
200 fn clear(&mut self) {
201 self.config
.shell().err_erase_line();
204 fn try_update_max_width(&mut self) {
205 if let Some(n
) = self.config
.shell().err_width() {
206 self.format
.max_width
= n
;
212 fn progress(&self, cur
: usize, max
: usize) -> Option
<String
> {
213 // Render the percentage at the far right and then figure how long the
215 let pct
= (cur
as f64) / (max
as f64);
216 let pct
= if !pct
.is_finite() { 0.0 }
else { pct }
;
217 let stats
= match self.style
{
218 ProgressStyle
::Percentage
=> format
!(" {:6.02}%", pct
* 100.0),
219 ProgressStyle
::Ratio
=> format
!(" {}/{}", cur
, max
),
221 let extra_len
= stats
.len() + 2 /* [ and ] */ + 15 /* status header */;
222 let display_width
= match self.width().checked_sub(extra_len
) {
227 let mut string
= String
::with_capacity(self.max_width
);
229 let hashes
= display_width
as f64 * pct
;
230 let hashes
= hashes
as usize;
234 for _
in 0..hashes
- 1 {
235 string
.push_str("=");
238 string
.push_str("=");
240 string
.push_str(">");
244 // Draw the empty space we have left to do
245 for _
in 0..(display_width
- hashes
) {
246 string
.push_str(" ");
248 string
.push_str("]");
249 string
.push_str(&stats
);
254 fn render(&self, string
: &mut String
, msg
: &str) {
255 let mut avail_msg_len
= self.max_width
- string
.len() - 15;
256 let mut ellipsis_pos
= 0;
257 if avail_msg_len
<= 3 {
260 for c
in msg
.chars() {
261 let display_width
= c
.width().unwrap_or(0);
262 if avail_msg_len
>= display_width
{
263 avail_msg_len
-= display_width
;
265 if avail_msg_len
>= 3 {
266 ellipsis_pos
= string
.len();
269 string
.truncate(ellipsis_pos
);
270 string
.push_str("...");
277 fn progress_status(&self, cur
: usize, max
: usize, msg
: &str) -> Option
<String
> {
278 let mut ret
= self.progress(cur
, max
)?
;
279 self.render(&mut ret
, msg
);
283 fn width(&self) -> usize {
284 cmp
::min(self.max_width
, self.max_print
)
288 impl<'cfg
> Drop
for State
<'cfg
> {
295 fn test_progress_status() {
296 let format
= Format
{
297 style
: ProgressStyle
::Ratio
,
302 format
.progress_status(0, 4, ""),
303 Some("[ ] 0/4".to_string())
306 format
.progress_status(1, 4, ""),
307 Some("[===> ] 1/4".to_string())
310 format
.progress_status(2, 4, ""),
311 Some("[========> ] 2/4".to_string())
314 format
.progress_status(3, 4, ""),
315 Some("[=============> ] 3/4".to_string())
318 format
.progress_status(4, 4, ""),
319 Some("[===================] 4/4".to_string())
323 format
.progress_status(3999, 4000, ""),
324 Some("[===========> ] 3999/4000".to_string())
327 format
.progress_status(4000, 4000, ""),
328 Some("[=============] 4000/4000".to_string())
332 format
.progress_status(3, 4, ": short message"),
333 Some("[=============> ] 3/4: short message".to_string())
336 format
.progress_status(3, 4, ": msg thats just fit"),
337 Some("[=============> ] 3/4: msg thats just fit".to_string())
340 format
.progress_status(3, 4, ": msg that's just fit"),
341 Some("[=============> ] 3/4: msg that's just...".to_string())
344 // combining diacritics have width zero and thus can fit max_width.
345 let zalgo_msg
= "z̸̧̢̗͉̝̦͍̱ͧͦͨ̑̅̌ͥ́͢a̢ͬͨ̽ͯ̅̑ͥ͋̏̑ͫ̄͢͏̫̝̪̤͎̱̣͍̭̞̙̱͙͍̘̭͚l̶̡̛̥̝̰̭̹̯̯̞̪͇̱̦͙͔̘̼͇͓̈ͨ͗ͧ̓͒ͦ̀̇ͣ̈ͭ͊͛̃̑͒̿̕͜g̸̷̢̩̻̻͚̠͓̞̥͐ͩ͌̑ͥ̊̽͋͐̐͌͛̐̇̑ͨ́ͅo͙̳̣͔̰̠̜͕͕̞̦̙̭̜̯̹̬̻̓͑ͦ͋̈̉͌̃ͯ̀̂͠ͅ ̸̡͎̦̲̖̤̺̜̮̱̰̥͔̯̅̏ͬ̂ͨ̋̃̽̈́̾̔̇ͣ̚͜͜h̡ͫ̐̅̿̍̀͜҉̛͇̭̹̰̠͙̞ẽ̶̙̹̳̖͉͎̦͂̋̓ͮ̔ͬ̐̀͂̌͑̒͆̚͜͠ ͓͓̟͍̮̬̝̝̰͓͎̼̻ͦ͐̾̔͒̃̓͟͟c̮̦͍̺͈͚̯͕̄̒͐̂͊̊͗͊ͤͣ̀͘̕͝͞o̶͍͚͍̣̮͌ͦ̽̑ͩ̅ͮ̐̽̏͗́͂̅ͪ͠m̷̧͖̻͔̥̪̭͉͉̤̻͖̩̤͖̘ͦ̂͌̆̂ͦ̒͊ͯͬ͊̉̌ͬ͝͡e̵̹̣͍̜̺̤̤̯̫̹̠̮͎͙̯͚̰̼͗͐̀̒͂̉̀̚͝͞s̵̲͍͙͖̪͓͓̺̱̭̩̣͖̣ͤͤ͂̎̈͗͆ͨͪ̆̈͗͝͠";
347 format
.progress_status(3, 4, zalgo_msg
),
348 Some("[=============> ] 3/4".to_string() + zalgo_msg
)
351 // some non-ASCII ellipsize test
353 format
.progress_status(3, 4, "_123456789123456e\u{301}\u{301}8\u{301}90a"),
354 Some("[=============> ] 3/4_123456789123456e\u{301}\u{301}...".to_string())
357 format
.progress_status(3, 4, ":每個漢字佔據了兩個字元"),
358 Some("[=============> ] 3/4:每個漢字佔據了...".to_string())
363 fn test_progress_status_percentage() {
364 let format
= Format
{
365 style
: ProgressStyle
::Percentage
,
370 format
.progress_status(0, 77, ""),
371 Some("[ ] 0.00%".to_string())
374 format
.progress_status(1, 77, ""),
375 Some("[ ] 1.30%".to_string())
378 format
.progress_status(76, 77, ""),
379 Some("[=============> ] 98.70%".to_string())
382 format
.progress_status(77, 77, ""),
383 Some("[===============] 100.00%".to_string())
388 fn test_progress_status_too_short() {
389 let format
= Format
{
390 style
: ProgressStyle
::Percentage
,
395 format
.progress_status(1, 1, ""),
396 Some("[] 100.00%".to_string())
399 let format
= Format
{
400 style
: ProgressStyle
::Percentage
,
405 format
.progress_status(1, 1, ""),