use std::io::Write as _;
use crate::{cli, commands};
pub(crate) fn scope_from_arg(scope: cli::ScopeArg) -> commands::plugin::Scope {
match scope {
cli::ScopeArg::User => commands::plugin::Scope::User,
cli::ScopeArg::Project => commands::plugin::Scope::Project,
}
}
pub(crate) fn targets_from_flags(f: &cli::AgentFlags) -> commands::plugin::Targets {
use commands::plugin::{Agent, Targets};
if f.all {
return Targets::All;
}
let mut agents = Vec::new();
let mut push = |on: bool, a: Agent| {
if on {
agents.push(a);
}
};
push(f.claude, Agent::ClaudeCode);
push(f.codex, Agent::Codex);
push(f.opencode, Agent::OpenCode);
push(f.cursor, Agent::Cursor);
push(f.windsurf, Agent::Windsurf);
push(f.aider, Agent::Aider);
push(f.zed, Agent::Zed);
push(f.gemini, Agent::Gemini);
push(f.copilot, Agent::Copilot);
push(f.continue_dev, Agent::Continue);
push(f.kiro, Agent::Kiro);
push(f.antigravity, Agent::Antigravity);
if agents.is_empty() {
Targets::Auto
} else {
Targets::Agents(agents)
}
}
pub(crate) fn print_diagnostics_stderr(diagnostics: &[zenith_core::Diagnostic]) {
for d in diagnostics {
eprintln!("{}", commands::format_diagnostic_line(d));
}
}
pub(crate) fn count_hard_diagnostics(diagnostics: &[zenith_core::Diagnostic]) -> usize {
diagnostics
.iter()
.filter(|d| d.severity == zenith_core::Severity::Error)
.count()
}
pub(crate) fn parse_spread_spec(spec: &str) -> Result<(usize, usize), String> {
let err = || {
format!(
"error: invalid --spread value {:?} (expected two 1-based page \
numbers like \"10-11\")",
spec
)
};
let (a_str, b_str) = spec.split_once('-').ok_or_else(err)?;
let a: usize = a_str.trim().parse().map_err(|_| err())?;
let b: usize = b_str.trim().parse().map_err(|_| err())?;
if a == 0 || b == 0 {
return Err(err());
}
Ok((a, b))
}
pub(crate) fn parse_at_spec(spec: Option<&str>) -> Result<(f64, f64), String> {
let spec = match spec {
None => return Ok((0.0, 0.0)),
Some(s) => s,
};
let err = || {
format!(
"error: invalid --at value {:?} (expected two comma-separated \
numbers like \"120,80\")",
spec
)
};
let (x_str, y_str) = spec.split_once(',').ok_or_else(err)?;
let x: f64 = x_str.trim().parse().map_err(|_| err())?;
let y: f64 = y_str.trim().parse().map_err(|_| err())?;
if !x.is_finite() || !y.is_finite() {
return Err(err());
}
Ok((x, y))
}
pub(crate) fn resolve_project_dir(path: Option<&std::path::Path>) -> Option<std::path::PathBuf> {
use std::path::Path;
match path {
None => Some(std::path::PathBuf::from(".")),
Some(p) if p.is_dir() => Some(p.to_path_buf()),
Some(p) => Some(
p.parent()
.filter(|parent| !parent.as_os_str().is_empty())
.unwrap_or(Path::new("."))
.to_path_buf(),
),
}
}
pub(crate) fn read_file(path: &std::path::Path) -> Result<String, String> {
std::fs::read(path)
.map_err(|e| format!("error reading '{}': {}", path.display(), e))
.and_then(|bytes| {
String::from_utf8(bytes)
.map_err(|_| format!("error: '{}' is not valid UTF-8", path.display()))
})
}
pub(crate) fn write_bytes(path: &std::path::Path, bytes: &[u8]) -> std::io::Result<()> {
let mut f = std::fs::File::create(path)?;
f.write_all(bytes)
}