1use anyhow::Context;
2use indicatif::ProgressStyle;
3use owo_colors::{OwoColorize, Stream::Stdout};
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6use std::{
7 io::{BufRead, Write},
8 sync::{Arc, Mutex, mpsc},
9};
10use strum::Display;
11
12mod file_term;
13pub mod markdown;
14mod null_term;
15mod secrets;
16mod util;
17pub use secrets::{DEFAULT_MAX_REDACTIONS, DEFAULT_MIN_SECRET_LENGTH, Secrets};
18
19#[derive(
20 Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Display, Default, Serialize, Deserialize,
21)]
22pub enum Level {
23 Trace,
24 Debug,
25 Message,
26 #[default]
27 Info,
28 App,
29 Passthrough,
30 Warning,
31 Error,
32 Silent,
33}
34
35#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct LogHeader {
37 pub(crate) command: Arc<str>,
38 pub(crate) working_directory: Option<Arc<str>>,
39 pub(crate) environment: HashMap<Arc<str>, HashMap<Arc<str>, Arc<str>>>,
40 pub(crate) arguments: Vec<Arc<str>>,
41 pub(crate) shell: Arc<str>,
42}
43
44#[derive(Debug, Clone, Copy, Default)]
45pub struct Verbosity {
46 pub level: Level,
47 pub is_show_progress_bars: bool,
48 pub is_show_elapsed_time: bool,
49 pub is_tty: bool,
50}
51
52const PROGRESS_PREFIX_WIDTH: usize = 0;
53
54pub struct Section<'a> {
55 pub printer: &'a mut Printer,
56}
57
58impl<'a> Section<'a> {
59 pub fn new(printer: &'a mut Printer, name: &str) -> anyhow::Result<Self> {
60 printer.write(format!("{}{}:", " ".repeat(printer.indent), name.bold()).as_str())?;
61 printer.shift_right();
62 Ok(Self { printer })
63 }
64}
65
66impl Drop for Section<'_> {
67 fn drop(&mut self) {
68 self.printer.shift_left();
69 }
70}
71
72pub struct MultiProgressBar {
73 lock: Arc<Mutex<()>>,
74 printer_verbosity: Verbosity,
75 start_time: std::time::Instant,
76 indent: usize,
77 max_width: usize,
78 progress_width: usize,
79 progress: Option<indicatif::ProgressBar>,
80 final_message: Option<Arc<str>>,
81 is_increasing: bool,
82 secrets: Secrets,
83}
84
85impl MultiProgressBar {
86 pub fn total(&self) -> Option<u64> {
87 if let Some(progress) = self.progress.as_ref() {
88 progress.length()
89 } else {
90 None
91 }
92 }
93
94 pub fn reset_elapsed(&mut self) {
95 if let Some(progress) = self.progress.as_mut() {
96 progress.reset_elapsed();
97 }
98 }
99
100 pub fn set_total(&mut self, total: u64) {
101 if let Some(progress) = self.progress.as_mut()
102 && let Some(length) = progress.length()
103 && length != total
104 {
105 let _lock = self.lock.lock().unwrap();
106 progress.set_length(total);
107 progress.set_position(0);
108 }
109 }
110
111 pub fn log(&mut self, verbosity: Level, message: &str) {
112 if util::is_verbosity_active(self.printer_verbosity, verbosity) {
113 let formatted_message = util::format_log(
114 self.indent,
115 self.max_width,
116 verbosity,
117 message,
118 self.printer_verbosity.is_show_elapsed_time,
119 self.start_time,
120 );
121 let _lock = self.lock.lock().unwrap();
122 if let Some(progress) = self.progress.as_ref() {
123 progress.println(formatted_message.as_str());
124 } else {
125 print!("{formatted_message}");
126 }
127 }
128 }
129
130 pub fn set_prefix(&mut self, message: &str) {
131 if let Some(progress) = self.progress.as_mut() {
132 let _lock = self.lock.lock().unwrap();
133 progress.set_prefix(message.to_owned());
134 }
135 }
136
137 fn construct_message(&self, message: &str) -> String {
138 let prefix_size = if let Some(progress) = self.progress.as_ref() {
139 progress.prefix().len()
140 } else {
141 0_usize
142 };
143 let length = if self.max_width > self.progress_width + prefix_size {
144 self.max_width - self.progress_width - prefix_size
145 } else {
146 0_usize
147 };
148 util::sanitize_output(message, length)
149 }
150
151 pub fn set_message(&mut self, message: &str) {
152 let constructed_message = self.construct_message(message);
153 if let Some(progress) = self.progress.as_mut() {
154 let _lock = self.lock.lock().unwrap();
155 progress.set_message(constructed_message);
156 }
157 }
158
159 pub fn set_ending_message(&mut self, message: &str) {
160 self.final_message = Some(self.construct_message(message).into());
161 }
162
163 pub fn set_ending_message_none(&mut self) {
164 self.final_message = None;
165 }
166
167 pub fn increment_with_overflow(&mut self, count: u64) {
168 let progress_total = self.total();
169 if let Some(progress) = self.progress.as_mut() {
170 let _lock = self.lock.lock().unwrap();
171 if self.is_increasing {
172 progress.inc(count);
173 if progress.position() == progress_total.unwrap_or(100) {
174 self.is_increasing = false;
175 }
176 } else if progress.position() >= count {
177 progress.set_position(progress.position() - count);
178 } else {
179 progress.set_position(0);
180 self.is_increasing = true;
181 }
182 }
183 }
184
185 pub fn decrement(&mut self, count: u64) {
186 if let Some(progress) = self.progress.as_mut() {
187 let _lock = self.lock.lock().unwrap();
188 if progress.position() >= count {
189 progress.set_position(progress.position() - count);
190 } else {
191 progress.set_position(0);
192 }
193 }
194 }
195
196 pub fn increment(&mut self, count: u64) {
197 if let Some(progress) = self.progress.as_mut() {
198 let _lock = self.lock.lock().unwrap();
199 progress.inc(count);
200 }
201 }
202
203 fn start_process(
204 &mut self,
205 command: &str,
206 options: &ExecuteOptions,
207 ) -> anyhow::Result<std::process::Child> {
208 if let Some(directory) = &options.working_directory
209 && !std::path::Path::new(directory.as_ref()).exists()
210 {
211 return Err(anyhow::anyhow!("Directory does not exist: {directory}"));
212 }
213
214 let child_process = options
215 .spawn(command)
216 .context(format!("Failed to spawn a child process using {command}"))?;
217 Ok(child_process)
218 }
219
220 pub fn execute_process(
221 &mut self,
222 command: &str,
223 options: ExecuteOptions,
224 ) -> anyhow::Result<Option<String>> {
225 self.set_message(&options.get_full_command(command));
226 let child_process = self
227 .start_process(command, &options)
228 .context(format!("Failed to start process {command}"))?;
229 let secrets = self.secrets.clone();
230 let result = util::monitor_process(command, child_process, self, &options, &secrets)
231 .context(format!("Command `{command}` failed to execute"))?;
232 Ok(result)
233 }
234}
235
236impl Drop for MultiProgressBar {
237 fn drop(&mut self) {
238 if let Some(message) = &self.final_message {
239 let constructed_message = self.construct_message(message);
240 if let Some(progress) = self.progress.as_mut() {
241 let _lock = self.lock.lock().unwrap();
242 progress.finish_with_message(constructed_message.bold().to_string());
243 }
244 }
245 }
246}
247
248pub struct MultiProgress<'a> {
249 pub printer: &'a mut Printer,
250 multi_progress: indicatif::MultiProgress,
251}
252
253impl<'a> MultiProgress<'a> {
254 pub fn new(printer: &'a mut Printer) -> Self {
255 let locker = printer.lock.clone();
256 let _lock = locker.lock().unwrap();
257
258 let draw_target = indicatif::ProgressDrawTarget::term_like_with_hz(
259 (printer.create_progress_printer)(),
260 10,
261 );
262
263 Self {
264 printer,
265 multi_progress: indicatif::MultiProgress::with_draw_target(draw_target),
266 }
267 }
268
269 pub fn add_progress(
270 &mut self,
271 prefix: &str,
272 total: Option<u64>,
273 finish_message: Option<&str>,
274 ) -> MultiProgressBar {
275 let _lock = self.printer.lock.lock().unwrap();
276
277 let template_string = "{elapsed_precise}|{bar:.cyan/blue}|{prefix} {msg}";
278
279 let (progress, progress_chars) = if let Some(total) = total {
280 let progress = indicatif::ProgressBar::new(total);
281 (progress, "#>-")
282 } else {
283 let progress = indicatif::ProgressBar::new(200);
284 (progress, "*>-")
285 };
286
287 progress.set_style(
288 ProgressStyle::with_template(template_string)
289 .unwrap()
290 .progress_chars(progress_chars),
291 );
292
293 let progress = if self.printer.verbosity.is_show_progress_bars {
294 let progress = self.multi_progress.add(progress);
295 let prefix = format!("{prefix}:");
296 progress.set_prefix(
297 format!("{prefix:PROGRESS_PREFIX_WIDTH$}")
298 .if_supports_color(Stdout, |text| text.bold())
299 .to_string(),
300 );
301 Some(progress)
302 } else {
303 None
304 };
305
306 MultiProgressBar {
307 lock: self.printer.lock.clone(),
308 printer_verbosity: self.printer.verbosity,
309 indent: self.printer.indent,
310 progress,
311 progress_width: 28, max_width: self.printer.max_width,
313 final_message: finish_message.map(|s| s.into()),
314 is_increasing: true,
315 start_time: self.printer.start_time,
316 secrets: self.printer.get_secrets(),
317 }
318 }
319}
320
321pub struct Heading<'a> {
322 pub printer: &'a mut Printer,
323}
324
325impl<'a> Heading<'a> {
326 pub fn new(printer: &'a mut Printer, name: &str) -> anyhow::Result<Self> {
327 printer.newline()?;
328 printer.enter_heading();
329 {
330 let heading = if printer.heading_count == 1 {
331 format!("{} {name}", "#".repeat(printer.heading_count))
332 .yellow()
333 .bold()
334 .to_string()
335 } else {
336 format!("{} {name}", "#".repeat(printer.heading_count))
337 .bold()
338 .to_string()
339 };
340 printer.write(heading.as_str())?;
341 printer.write("\n")?;
342 }
343 Ok(Self { printer })
344 }
345}
346
347impl Drop for Heading<'_> {
348 fn drop(&mut self) {
349 self.printer.exit_heading();
350 }
351}
352
353#[derive(Clone, Debug)]
354pub struct ExecuteOptions {
355 pub label: Arc<str>,
356 pub is_return_stdout: bool,
357 pub working_directory: Option<Arc<str>>,
358 pub environment: Vec<(Arc<str>, Arc<str>)>,
359 pub arguments: Vec<Arc<str>>,
360 pub log_file_path: Option<Arc<str>>,
361 pub clear_environment: bool,
362 pub process_started_with_id: Option<fn(&str, u32)>,
363 pub log_level: Option<Level>,
364 pub timeout: Option<std::time::Duration>,
365}
366
367impl Default for ExecuteOptions {
368 fn default() -> Self {
369 Self {
370 label: "working".into(),
371 is_return_stdout: false,
372 working_directory: None,
373 environment: vec![],
374 arguments: vec![],
375 log_file_path: None,
376 clear_environment: false,
377 process_started_with_id: None,
378 log_level: None,
379 timeout: None,
380 }
381 }
382}
383
384impl ExecuteOptions {
385 pub(crate) fn process_child_output<OutputType: std::io::Read + Send + 'static>(
386 output: OutputType,
387 ) -> anyhow::Result<(std::thread::JoinHandle<()>, mpsc::Receiver<String>)> {
388 let (tx, rx) = mpsc::channel::<String>();
389
390 let thread = std::thread::spawn(move || {
391 use std::io::BufReader;
392 let reader = BufReader::new(output);
393 for line in reader.lines() {
394 let line = line.unwrap();
395 tx.send(line).unwrap();
396 }
397 });
398
399 Ok((thread, rx))
400 }
401
402 fn spawn(&self, command: &str) -> anyhow::Result<std::process::Child> {
403 use std::process::{Command, Stdio};
404 let mut process = Command::new(command);
405
406 if self.clear_environment {
407 process.env_clear();
408 }
409
410 for argument in &self.arguments {
411 process.arg(argument.as_ref());
412 }
413
414 if let Some(directory) = &self.working_directory {
415 process.current_dir(directory.as_ref());
416 }
417
418 for (key, value) in self.environment.iter() {
419 process.env(key.as_ref(), value.as_ref());
420 }
421
422 let result = process
423 .stdout(Stdio::piped())
424 .stderr(Stdio::piped())
425 .stdin(Stdio::null())
426 .spawn()
427 .context(format!("while spawning piped {command}"))?;
428
429 if let Some(callback) = self.process_started_with_id.as_ref() {
430 callback(self.label.as_ref(), result.id());
431 }
432
433 Ok(result)
434 }
435
436 pub fn get_full_command(&self, command: &str) -> String {
437 format!("{command} {}", self.arguments.join(" "))
438 }
439
440 pub fn get_full_command_in_working_directory(&self, command: &str) -> String {
441 format!(
442 "{} {command} {}",
443 if let Some(directory) = &self.working_directory {
444 directory
445 } else {
446 ""
447 },
448 self.arguments.join(" "),
449 )
450 }
451}
452
453trait PrinterTrait: std::io::Write + indicatif::TermLike {}
454impl<W: std::io::Write + indicatif::TermLike> PrinterTrait for W {}
455
456pub struct Printer {
457 pub verbosity: Verbosity,
458 pub secrets: Vec<Arc<str>>,
459 pub redacted: Arc<str>,
460 pub max_redactions: usize,
461 pub min_secret_length: usize,
462 lock: Arc<Mutex<()>>,
463 indent: usize,
464 heading_count: usize,
465 max_width: usize,
466 writer: Box<dyn PrinterTrait>,
467 start_time: std::time::Instant,
468 create_progress_printer: fn() -> Box<dyn PrinterTrait>,
469}
470
471impl Printer {
472 pub fn get_log_divider() -> Arc<str> {
473 "=".repeat(80).into()
474 }
475
476 pub fn get_terminal_width() -> usize {
477 const ASSUMED_WIDTH: usize = 80;
478 if let Some((width, _)) = terminal_size::terminal_size() {
479 width.0 as usize
480 } else {
481 ASSUMED_WIDTH
482 }
483 }
484
485 pub fn new_stdout() -> Self {
486 let max_width = Self::get_terminal_width();
487 Self {
488 indent: 0,
489 lock: Arc::new(Mutex::new(())),
490 verbosity: Verbosity::default(),
491 heading_count: 0,
492 max_width,
493 writer: Box::new(console::Term::stdout()),
494 create_progress_printer: || Box::new(console::Term::stdout()),
495 start_time: std::time::Instant::now(),
496 secrets: Vec::new(),
497 redacted: "<REDACTED>".into(),
498 max_redactions: DEFAULT_MAX_REDACTIONS,
499 min_secret_length: DEFAULT_MIN_SECRET_LENGTH,
500 }
501 }
502
503 pub fn new_file(path: &str) -> anyhow::Result<Self> {
504 let file_writer = file_term::FileTerm::new(path)?;
505 Ok(Self {
506 indent: 0,
507 lock: Arc::new(Mutex::new(())),
508 verbosity: Verbosity::default(),
509 heading_count: 0,
510 max_width: 65535,
511 writer: Box::new(file_writer),
512 create_progress_printer: || Box::new(null_term::NullTerm {}),
513 start_time: std::time::Instant::now(),
514 secrets: Vec::new(),
515 redacted: "<REDACTED>".into(),
516 max_redactions: DEFAULT_MAX_REDACTIONS,
517 min_secret_length: DEFAULT_MIN_SECRET_LENGTH,
518 })
519 }
520
521 pub fn new_null_term() -> Self {
522 Self {
523 indent: 0,
524 lock: Arc::new(Mutex::new(())),
525 verbosity: Verbosity::default(),
526 heading_count: 0,
527 max_width: 80,
528 writer: Box::new(null_term::NullTerm {}),
529 create_progress_printer: || Box::new(null_term::NullTerm {}),
530 start_time: std::time::Instant::now(),
531 secrets: Vec::new(),
532 redacted: "<REDACTED>".into(),
533 max_redactions: DEFAULT_MAX_REDACTIONS,
534 min_secret_length: DEFAULT_MIN_SECRET_LENGTH,
535 }
536 }
537
538 pub fn raw(&mut self, message: &str) -> anyhow::Result<()> {
539 let _lock = self.lock.lock().unwrap();
540 write!(self.writer, "{message}")?;
541 Ok(())
542 }
543
544 pub(crate) fn write(&mut self, message: &str) -> anyhow::Result<()> {
545 let redacted = self.get_secrets().redact(message.into());
546 let _lock = self.lock.lock().unwrap();
547 write!(self.writer, "{redacted}")?;
548 Ok(())
549 }
550
551 pub fn newline(&mut self) -> anyhow::Result<()> {
552 self.write("\n")?;
553 Ok(())
554 }
555
556 pub fn trace<Type: Serialize>(&mut self, name: &str, value: &Type) -> anyhow::Result<()> {
557 if util::is_verbosity_active(self.verbosity, Level::Trace) {
558 self.object(name, value)
559 } else {
560 Ok(())
561 }
562 }
563
564 pub fn debug<Type: Serialize>(&mut self, name: &str, value: &Type) -> anyhow::Result<()> {
565 if util::is_verbosity_active(self.verbosity, Level::Debug) {
566 self.object(name, value)
567 } else {
568 Ok(())
569 }
570 }
571
572 pub fn message<Type: Serialize>(&mut self, name: &str, value: &Type) -> anyhow::Result<()> {
573 if util::is_verbosity_active(self.verbosity, Level::Message) {
574 self.object(name, value)
575 } else {
576 Ok(())
577 }
578 }
579
580 pub fn info<Type: Serialize>(&mut self, name: &str, value: &Type) -> anyhow::Result<()> {
581 if util::is_verbosity_active(self.verbosity, Level::Info) {
582 self.object(name, value)
583 } else {
584 Ok(())
585 }
586 }
587
588 pub fn warning<Type: Serialize>(&mut self, name: &str, value: &Type) -> anyhow::Result<()> {
589 if util::is_verbosity_active(self.verbosity, Level::Warning) {
590 self.object(name.yellow().to_string().as_str(), value)
591 } else {
592 Ok(())
593 }
594 }
595
596 pub fn error<Type: Serialize>(&mut self, name: &str, value: &Type) -> anyhow::Result<()> {
597 if util::is_verbosity_active(self.verbosity, Level::Error) {
598 self.object(name.red().to_string().as_str(), value)
599 } else {
600 Ok(())
601 }
602 }
603
604 pub fn log(&mut self, level: Level, message: &str) -> anyhow::Result<()> {
605 if util::is_verbosity_active(self.verbosity, level) {
606 self.write(
607 util::format_log(
608 self.indent,
609 self.max_width,
610 level,
611 message,
612 self.verbosity.is_show_elapsed_time,
613 self.start_time,
614 )
615 .as_str(),
616 )
617 } else {
618 Ok(())
619 }
620 }
621
622 pub fn code_block(&mut self, name: &str, content: &str) -> anyhow::Result<()> {
623 self.write(format!("```{name}\n{content}```\n").as_str())?;
624 Ok(())
625 }
626
627 fn get_secrets(&self) -> Secrets {
628 Secrets {
629 secrets: self.secrets.clone(),
630 redacted: self.redacted.clone(),
631 max_redactions: self.max_redactions,
632 min_secret_length: self.min_secret_length,
633 }
634 }
635
636 fn object<Type: Serialize>(&mut self, name: &str, value: &Type) -> anyhow::Result<()> {
637 let value = serde_json::to_value(value).context("failed to serialize as JSON")?;
638
639 if self.verbosity.level <= Level::Message && value == serde_json::Value::Null {
640 return Ok(());
641 }
642
643 self.write(
644 format!(
645 "{}{}: ",
646 " ".repeat(self.indent),
647 name.if_supports_color(Stdout, |text| text.bold())
648 )
649 .as_str(),
650 )?;
651
652 self.print_value(&value)?;
653 Ok(())
654 }
655
656 fn enter_heading(&mut self) {
657 self.heading_count += 1;
658 }
659
660 fn exit_heading(&mut self) {
661 self.heading_count -= 1;
662 }
663
664 fn shift_right(&mut self) {
665 self.indent += 2;
666 }
667
668 fn shift_left(&mut self) {
669 self.indent -= 2;
670 }
671
672 fn print_value(&mut self, value: &serde_json::Value) -> anyhow::Result<()> {
673 match value {
674 serde_json::Value::Object(map) => {
675 self.write("\n")?;
676 self.shift_right();
677 for (key, value) in map {
678 let is_skip =
679 *value == serde_json::Value::Null && self.verbosity.level > Level::Message;
680 if !is_skip {
681 {
682 self.write(
683 format!(
684 "{}{}: ",
685 " ".repeat(self.indent),
686 key.if_supports_color(Stdout, |text| text.bold())
687 )
688 .as_str(),
689 )?;
690 }
691 self.print_value(value)?;
692 }
693 }
694 self.shift_left();
695 }
696 serde_json::Value::Array(array) => {
697 self.write("\n")?;
698 self.shift_right();
699 for (index, value) in array.iter().enumerate() {
700 self.write(format!("{}[{index}]: ", " ".repeat(self.indent)).as_str())?;
701 self.print_value(value)?;
702 }
703 self.shift_left();
704 }
705 serde_json::Value::Null => {
706 self.write("null\n")?;
707 }
708 serde_json::Value::Bool(value) => {
709 self.write(format!("{value}\n").as_str())?;
710 }
711 serde_json::Value::Number(value) => {
712 self.write(format!("{value}\n").as_str())?;
713 }
714 serde_json::Value::String(value) => {
715 self.write(format!("{value}\n").as_str())?;
716 }
717 }
718
719 Ok(())
720 }
721
722 pub fn start_process(
723 &mut self,
724 command: &str,
725 options: &ExecuteOptions,
726 ) -> anyhow::Result<std::process::Child> {
727 let args = options.arguments.join(" ");
728 let full_command = format!("{command} {args}");
729
730 self.info("execute", &full_command)?;
731 if let Some(directory) = &options.working_directory {
732 self.info("directory", &directory)?;
733 if !std::path::Path::new(directory.as_ref()).exists() {
734 return Err(anyhow::anyhow!("Directory does not exist: {directory}"));
735 }
736 }
737
738 let child_process = options
739 .spawn(command)
740 .context(format!("while spawning {command}"))?;
741 Ok(child_process)
742 }
743
744 pub fn execute_process(
745 &mut self,
746 command: &str,
747 options: ExecuteOptions,
748 ) -> anyhow::Result<Option<String>> {
749 let section = Section::new(self, command)?;
750 let child_process = section
751 .printer
752 .start_process(command, &options)
753 .context(format!("Faild to execute process: {command}"))?;
754 let mut multi_progress = MultiProgress::new(section.printer);
755 let mut progress_bar = multi_progress.add_progress("progress", None, None);
756 let secrets = multi_progress.printer.get_secrets();
757 let result = util::monitor_process(
758 command,
759 child_process,
760 &mut progress_bar,
761 &options,
762 &secrets,
763 )
764 .context(format!("Command `{command}` failed to execute"))?;
765
766 Ok(result)
767 }
768}
769
770#[cfg(test)]
771mod tests {
772 use super::*;
773
774 #[derive(Serialize)]
775 pub struct Test {
776 pub name: String,
777 pub age: u32,
778 pub alive: bool,
779 pub dead: bool,
780 pub children: f64,
781 }
782
783 #[test]
784 fn printer() {
785 let mut printer = Printer::new_stdout();
786 let mut options = ExecuteOptions::default();
787 options.arguments.push("-alt".into());
788
789 let runtime =
790 tokio::runtime::Runtime::new().expect("Internal Error: Failed to create runtime");
791
792 let (async_sender, sync_receiver) = flume::bounded(1);
793 runtime.spawn(async move {
794 async_sender.send_async(10).await.expect("Failed to send");
795 });
796 let received = sync_receiver.recv().expect("Failed to receive");
797
798 drop(runtime);
799
800 printer.info("Received", &received).unwrap();
801
802 printer.execute_process("/bin/ls", options).unwrap();
803
804 {
805 let mut heading = Heading::new(&mut printer, "First").unwrap();
806 {
807 let section = Section::new(&mut heading.printer, "PersonWrapper").unwrap();
808 section
809 .printer
810 .object(
811 "Person",
812 &Test {
813 name: "John".to_string(),
814 age: 30,
815 alive: true,
816 dead: false,
817 children: 2.5,
818 },
819 )
820 .unwrap();
821 }
822
823 let mut sub_heading = Heading::new(&mut heading.printer, "Second").unwrap();
824
825 let mut sub_section = Section::new(&mut sub_heading.printer, "PersonWrapper").unwrap();
826 sub_section.printer.object("Hello", &"World").unwrap();
827
828 {
829 let mut multi_progress = MultiProgress::new(&mut sub_section.printer);
830 let mut first = multi_progress.add_progress("First", Some(10), None);
831 let mut second = multi_progress.add_progress("Second", Some(50), None);
832 let mut third = multi_progress.add_progress("Third", Some(100), None);
833
834 let first_handle = std::thread::spawn(move || {
835 first.set_ending_message("Done!");
836 for index in 0..10 {
837 first.increment(1);
838 if index == 5 {
839 first.set_message("half way");
840 }
841 std::thread::sleep(std::time::Duration::from_millis(100));
842 }
843 });
844
845 let second_handle = std::thread::spawn(move || {
846 for index in 0..50 {
847 second.increment(1);
848 if index == 25 {
849 second.set_message("half way");
850 }
851 std::thread::sleep(std::time::Duration::from_millis(10));
852 }
853 });
854
855 for _ in 0..100 {
856 third.increment(1);
857 std::thread::sleep(std::time::Duration::from_millis(10));
858 }
859
860 first_handle.join().unwrap();
861 second_handle.join().unwrap();
862 }
863 }
864
865 {
866 let runtime =
867 tokio::runtime::Runtime::new().expect("Internal Error: Failed to create runtime");
868
869 let heading = Heading::new(&mut printer, "Async").unwrap();
870
871 let mut multi_progress = MultiProgress::new(heading.printer);
872
873 let mut handles = Vec::new();
874
875 let task1_progress = multi_progress.add_progress("Task1", Some(30), None);
876 let task2_progress = multi_progress.add_progress("Task2", Some(30), None);
877 let task1 = async move {
878 let mut progress = task1_progress;
879 progress.set_message("Task1a");
880 for _ in 0..10 {
881 tokio::time::sleep(std::time::Duration::from_millis(100)).await;
882 progress.increment(1);
883 }
884
885 progress.set_message("Task1b");
886 for _ in 0..10 {
887 tokio::time::sleep(std::time::Duration::from_millis(100)).await;
888 progress.increment(1);
889 }
890
891 progress.set_message("Task1c");
892 for _ in 0..10 {
893 tokio::time::sleep(std::time::Duration::from_millis(100)).await;
894 progress.increment(1);
895 }
896 ()
897 };
898 handles.push(runtime.spawn(task1));
899
900 let task2 = async move {
901 let mut progress = task2_progress;
902 progress.set_message("Task2a");
903 for _ in 0..10 {
904 tokio::time::sleep(std::time::Duration::from_millis(100)).await;
905 progress.increment(1);
906 }
907
908 progress.set_message("Task2b");
909 for _ in 0..10 {
910 tokio::time::sleep(std::time::Duration::from_millis(100)).await;
911 progress.increment(1);
912 }
913
914 progress.set_message("Task2c");
915 for _ in 0..10 {
916 tokio::time::sleep(std::time::Duration::from_millis(100)).await;
917 progress.increment(1);
918 }
919 ()
920 };
921 handles.push(runtime.spawn(task2));
922
923 for handle in handles {
924 runtime.block_on(handle).unwrap();
925 }
926 }
927 }
928}