starbase_console 0.6.26

Console reporting layer.
Documentation
use crate::console_error::ConsoleError;
use crate::reporter::*;
use crate::stream::*;
#[cfg(feature = "ui")]
use crate::theme::ConsoleTheme;
use std::fmt;
use std::ops::Deref;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::thread::JoinHandle;
use tracing::trace;

pub struct Console<R: Reporter> {
    pub err: ConsoleStream,
    err_handle: Option<JoinHandle<()>>,

    pub out: ConsoleStream,
    out_handle: Option<JoinHandle<()>>,

    quiet: Arc<AtomicBool>,
    reporter: Option<Arc<R>>,

    #[cfg(feature = "ui")]
    theme: ConsoleTheme,
}

impl<R: Reporter> Console<R> {
    pub fn new(quiet: bool) -> Self {
        trace!("Creating buffered console");

        let quiet = Arc::new(AtomicBool::new(quiet));

        let mut err = ConsoleStream::new(ConsoleStreamType::Stderr);
        err.quiet = Some(Arc::clone(&quiet));

        let mut out = ConsoleStream::new(ConsoleStreamType::Stdout);
        out.quiet = Some(Arc::clone(&quiet));

        Self {
            err_handle: err.handle.take(),
            err,
            out_handle: out.handle.take(),
            out,
            quiet,
            reporter: None,
            #[cfg(feature = "ui")]
            theme: Default::default(),
        }
    }

    pub fn new_testing() -> Self {
        Self {
            err: ConsoleStream::new_testing(ConsoleStreamType::Stderr),
            err_handle: None,
            out: ConsoleStream::new_testing(ConsoleStreamType::Stdout),
            out_handle: None,
            quiet: Arc::new(AtomicBool::new(false)),
            reporter: None,
            #[cfg(feature = "ui")]
            theme: Default::default(),
        }
    }

    pub fn close(&mut self) -> Result<(), ConsoleError> {
        trace!("Closing console and flushing buffered output");

        self.err.close()?;
        self.out.close()?;

        if let Some(handle) = self.err_handle.take() {
            let _ = handle.join();
        }

        if let Some(handle) = self.out_handle.take() {
            let _ = handle.join();
        }

        Ok(())
    }

    pub fn quiet(&self) {
        self.set_quiet(true);
    }

    pub fn stderr(&self) -> ConsoleStream {
        self.err.clone()
    }

    pub fn stdout(&self) -> ConsoleStream {
        self.out.clone()
    }

    pub fn reporter(&self) -> Arc<R> {
        Arc::clone(
            self.reporter
                .as_ref()
                .expect("Reporter has not been configured for the current console!"),
        )
    }

    #[cfg(feature = "ui")]
    pub fn theme(&self) -> ConsoleTheme {
        self.theme.clone()
    }

    pub fn set_reporter(&mut self, mut reporter: R) {
        reporter.inherit_streams(self.stderr(), self.stdout());

        #[cfg(feature = "ui")]
        reporter.inherit_theme(self.theme());

        self.reporter = Some(Arc::new(reporter));
    }

    #[cfg(feature = "ui")]
    pub fn set_theme(&mut self, theme: crate::theme::ConsoleTheme) {
        if let Some(arc_reporter) = &mut self.reporter {
            if let Some(reporter) = Arc::get_mut(arc_reporter) {
                reporter.inherit_theme(theme.clone());
            }
        }

        self.theme = theme;
    }

    pub fn set_quiet(&self, value: bool) {
        self.quiet.store(value, Ordering::Release);
    }
}

impl<R: Reporter> Clone for Console<R> {
    fn clone(&self) -> Self {
        Self {
            err: self.err.clone(),
            err_handle: None,
            out: self.out.clone(),
            out_handle: None,
            quiet: self.quiet.clone(),
            reporter: self.reporter.clone(),
            #[cfg(feature = "ui")]
            theme: self.theme.clone(),
        }
    }
}

impl<R: Reporter> fmt::Debug for Console<R> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let mut dbg = f.debug_struct("Console");

        dbg.field("err", &self.err)
            .field("out", &self.out)
            .field("quiet", &self.quiet)
            .field("reporter", &self.reporter);

        #[cfg(feature = "ui")]
        dbg.field("theme", &self.theme);

        dbg.finish()
    }
}

impl<R: Reporter> Deref for Console<R> {
    type Target = R;

    fn deref(&self) -> &Self::Target {
        self.reporter
            .as_ref()
            .expect("Reporter has not been configured for the current console!")
    }
}