use anyhow::Result;
use serde::Serialize;
use crate::cli::{cli_args::Cli, should_output_json};
pub fn real_or_none(value: &str) -> Option<&str> {
let trimmed = value.trim();
if trimmed.is_empty() || trimmed.eq_ignore_ascii_case("unknown") {
None
} else {
Some(value)
}
}
pub fn actor_display(provider: Option<&str>, model: Option<&str>) -> Option<String> {
let provider = provider.and_then(real_or_none);
let model = model.and_then(real_or_none);
match (provider, model) {
(Some(p), Some(m)) => Some(format!("{p}/{m}")),
(Some(p), None) => Some(p.to_string()),
(None, Some(m)) => Some(m.to_string()),
(None, None) => None,
}
}
pub fn preview_list(items: &[String], total: usize) -> String {
const PREVIEW: usize = 3;
let visible: Vec<&str> = items.iter().take(PREVIEW).map(String::as_str).collect();
let suffix = if total > visible.len() {
format!(", … +{} more", total - visible.len())
} else {
String::new()
};
format!("{}{suffix}", visible.join(", "))
}
#[derive(Clone, Debug, Default)]
pub struct RenderOpts {
pub short: bool,
pub no_color: bool,
pub limit: Option<usize>,
}
pub trait RenderOutput: Serialize {
fn render_text<W: std::io::Write>(&self, w: &mut W, opts: RenderOpts) -> std::io::Result<()>;
}
pub fn emit<T: RenderOutput>(cli: &Cli, cfg: Option<&repo::RepoConfig>, out: &T) -> Result<()> {
let stdout = std::io::stdout();
let mut handle = stdout.lock();
if should_output_json(cli, cfg) {
serde_json::to_writer(&mut handle, out)?;
use std::io::Write;
let _ = handle.write_all(b"\n");
} else {
out.render_text(&mut handle, RenderOpts::default())?;
}
Ok(())
}
pub fn emit_with_opts<T: RenderOutput>(
cli: &Cli,
cfg: Option<&repo::RepoConfig>,
out: &T,
opts: RenderOpts,
) -> Result<()> {
let stdout = std::io::stdout();
let mut handle = stdout.lock();
if should_output_json(cli, cfg) {
serde_json::to_writer(&mut handle, out)?;
use std::io::Write;
let _ = handle.write_all(b"\n");
} else {
out.render_text(&mut handle, opts)?;
}
Ok(())
}