use std::fmt;
use std::io::IsTerminal;
use hm_plugin_protocol::BuildEvent;
#[must_use]
pub fn color_enabled(no_color_flag: bool) -> bool {
!no_color_flag && std::env::var_os("NO_COLOR").is_none() && std::io::stderr().is_terminal()
}
#[must_use]
pub fn stderr_interactive() -> bool {
std::io::stderr().is_terminal()
}
#[must_use]
pub fn stdout_piped() -> bool {
!std::io::stdout().is_terminal()
}
pub mod human;
pub mod json;
pub mod progress;
pub mod spinner;
pub use human::HumanRenderer;
pub use json::JsonRenderer;
pub use progress::ProgressRenderer;
pub trait OutputRenderer: Send + fmt::Debug {
fn on_event(&mut self, event: &BuildEvent);
}
#[derive(Debug, Clone)]
pub enum OutputMode {
Human {
color: bool,
interactive: bool,
},
Json,
}
impl OutputMode {
#[must_use]
pub const fn is_json(&self) -> bool {
matches!(self, Self::Json)
}
#[must_use]
pub const fn is_human(&self) -> bool {
matches!(self, Self::Human { .. })
}
#[must_use]
pub const fn color_enabled(&self) -> bool {
matches!(self, Self::Human { color: true, .. })
}
#[must_use]
pub const fn interactive(&self) -> bool {
matches!(
self,
Self::Human {
interactive: true,
..
}
)
}
#[must_use]
pub const fn use_hyperlinks(&self) -> bool {
matches!(
self,
Self::Human {
interactive: true,
color: true
}
)
}
}
pub fn renderer_for(
format: &str,
color: bool,
logs: bool,
) -> anyhow::Result<Box<dyn OutputRenderer>> {
match format {
"json" => Ok(Box::new(JsonRenderer::new(std::io::stdout()))),
"human" if logs => Ok(Box::new(HumanRenderer::new(std::io::stderr(), color))),
"human" => Ok(Box::new(ProgressRenderer::new(std::io::stderr(), color))),
other => anyhow::bail!("unknown --format '{other}'\n available: human, json"),
}
}
pub async fn drive(
mut renderer: Box<dyn OutputRenderer>,
mut rx: tokio::sync::mpsc::Receiver<BuildEvent>,
) {
while let Some(ev) = rx.recv().await {
let end = ev.is_build_end();
renderer.on_event(&ev);
if end {
break;
}
}
}
pub async fn drive_stream(
mut renderer: Box<dyn OutputRenderer>,
mut events: futures::stream::BoxStream<'static, BuildEvent>,
) {
use futures::StreamExt as _;
while let Some(ev) = events.next().await {
let end = ev.is_build_end();
renderer.on_event(&ev);
if end {
break;
}
}
}