Skip to main content

spaces_printer/
lib.rs

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, // This is the default from indicatif?
312            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}