use std::ffi::OsString;
use std::io::{ErrorKind, Write};
use std::sync::{Arc, Mutex};
use clap::Parser;
use termhog::{Diagnostic, Error, HogCommand, Reporter, TermHog};
type Messages = Arc<Mutex<Vec<String>>>;
#[derive(Parser)]
#[command(name = "termhog", version)]
struct Cli {
#[arg(long, env = "POSTHOG_API_KEY", hide_env_values = true)]
api_key: String,
#[arg(long, env = "POSTHOG_HOST", hide_env_values = true)]
host: Option<String>,
#[arg(long, env = "POSTHOG_UI_HOST", hide_env_values = true)]
ui_host: Option<String>,
#[arg(long, env = "POSTHOG_DISTINCT_ID", hide_env_values = true)]
distinct_id: Option<String>,
#[arg(long, env = "POSTHOG_SESSION_ID", hide_env_values = true)]
session_id: Option<String>,
#[cfg(windows)]
#[arg(long, env = "TERMHOG_EXECUTABLE_DIR")]
executable_dir: Option<std::path::PathBuf>,
#[arg(trailing_var_arg = true, required = true, num_args = 1.., value_name = "COMMAND")]
command: Vec<OsString>,
}
impl Cli {
fn to_termhog(&self, reporter: Arc<dyn Reporter>) -> TermHog {
let set = |value: &Option<String>| value.clone().filter(|v| !v.is_empty());
let mut settings = TermHog::new(&self.api_key).reporter(reporter);
if let Some(host) = set(&self.host) {
settings = settings.ingest_host(host);
}
if let Some(host) = set(&self.ui_host) {
settings = settings.ui_host(host);
}
if let Some(id) = set(&self.distinct_id) {
settings = settings.distinct_id(id);
}
if let Some(id) = set(&self.session_id) {
settings = settings.session_id(id);
}
#[cfg(windows)]
if let Some(dir) = self
.executable_dir
.clone()
.filter(|d| !d.as_os_str().is_empty())
{
settings = settings.executable_dir(dir);
}
let dim = |key| std::env::var(key).ok()?.parse().ok();
if let (Some(cols), Some(rows)) = (dim("COLUMNS"), dim("LINES")) {
settings = settings.fallback_size(cols, rows);
}
settings
}
}
macro_rules! note {
($($arg:tt)*) => {{
let _ = writeln!(std::io::stderr(), "termhog: {}", format_args!($($arg)*));
}};
}
fn main() {
termhog::init();
let cli = Cli::parse();
let messages = Messages::default();
let reporter = {
let messages = Arc::clone(&messages);
move |diagnostic: &Diagnostic| {
if let Ok(mut messages) = messages.lock() {
messages.push(diagnostic.to_string());
}
}
};
let mut command = HogCommand::new(&cli.command[0]);
command.args(&cli.command[1..]);
let outcome = cli.to_termhog(Arc::new(reporter)).status(&mut command);
if let Ok(messages) = messages.lock() {
for message in messages.iter() {
note!("{message}");
}
}
match outcome {
Ok(outcome) => {
note!("replay → {}", outcome.replay_url);
outcome.exit();
}
Err(e) => {
note!("{e}");
let code = match &e {
Error::Spawn { source, .. } if source.kind() == ErrorKind::NotFound => 127,
Error::Spawn { .. } => 126,
_ => 1,
};
std::process::exit(code);
}
}
}