use super::{Draft, FileIo};
use crate::xdg::Env;
use std::path::{Path, PathBuf};
mod effects;
pub use effects::RealBzRunner;
mod providers;
pub use providers::{ProviderRow, ProviderRowView, provider_rows, row_names, row_views};
#[cfg(test)]
mod tests;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BzOutcome {
pub success: bool,
pub stdout: String,
pub stderr: String,
}
pub trait BzRunner {
fn dump_config_at(&self, config: &Path) -> BzOutcome;
fn dump_config_effective(&self) -> BzOutcome;
fn providers(&self) -> Vec<ProviderRow>;
fn list_models(&self, provider: &str) -> BzOutcome;
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Applied {
Ok,
Rejected { stderr: String },
Conflict,
Io { error: String },
}
pub const BUILT_IN_ROWS_HINT: &str =
"built-in provider rows are compiled into bz and are not shown in this file";
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BrazenPaths {
pub config: PathBuf,
pub credentials_dir: PathBuf,
pub models_cache_dir: PathBuf,
}
impl BrazenPaths {
pub fn in_wall(wall: &Path) -> Self {
let brazen = wall.join("brazen");
Self {
config: brazen.join("config.toml"),
credentials_dir: brazen.join("credentials"),
models_cache_dir: brazen.join("models"),
}
}
pub fn of(env: &Env) -> Option<Self> {
env.wall().map(|w| Self::in_wall(&w))
}
}
pub fn credential_presence(
dir: &Path,
rows: &[ProviderRow],
io: &dyn FileIo,
) -> Vec<(String, bool)> {
rows.iter()
.map(|row| {
let path = dir.join(format!("{}.json", row.name));
(row.name.clone(), io.exists(&path))
})
.collect()
}
pub fn model_cache_at(
dir: &Path,
provider: &str,
io: &dyn FileIo,
) -> std::io::Result<Option<String>> {
Ok(io
.read(&dir.join(format!("{provider}.json")))?
.map(|b| String::from_utf8_lossy(&b).into_owned()))
}
#[derive(Debug, Clone)]
pub struct BrazenEditor {
paths: BrazenPaths,
draft: Draft,
}
impl BrazenEditor {
pub fn load(paths: BrazenPaths, io: &dyn FileIo) -> std::io::Result<Self> {
let draft = Draft::load(paths.config.clone(), io)?;
Ok(Self { paths, draft })
}
#[cfg(test)]
pub(crate) fn draft(&self) -> &str {
self.draft.text()
}
pub(crate) fn draft_mut(&mut self) -> &mut String {
self.draft.text_mut()
}
pub fn set_draft(&mut self, text: String) {
self.draft.set(text);
}
pub fn reload(&mut self, io: &dyn FileIo) -> std::io::Result<()> {
self.draft.reload(io)
}
pub fn refresh(&mut self, io: &dyn FileIo) -> std::io::Result<bool> {
self.draft.refresh(io)
}
pub fn apply(&mut self, runner: &dyn BzRunner, io: &dyn FileIo) -> Applied {
match self.apply_inner(runner, io) {
Ok(applied) => applied,
Err(e) => Applied::Io {
error: e.to_string(),
},
}
}
fn apply_inner(&mut self, runner: &dyn BzRunner, io: &dyn FileIo) -> std::io::Result<Applied> {
let staged = self.draft.stage(io)?;
let gate = runner.dump_config_at(staged.temp());
if !gate.success {
staged.discard(io)?;
return Ok(Applied::Rejected {
stderr: gate.stderr,
});
}
if self.draft.commit(staged, io)? {
Ok(Applied::Ok)
} else {
Ok(Applied::Conflict)
}
}
pub fn effective(&self, runner: &dyn BzRunner) -> BzOutcome {
runner.dump_config_effective()
}
pub fn model_cache(&self, provider: &str, io: &dyn FileIo) -> std::io::Result<Option<String>> {
model_cache_at(&self.paths.models_cache_dir, provider, io)
}
pub fn refresh_models(&self, provider: &str, runner: &dyn BzRunner) -> BzOutcome {
runner.list_models(provider)
}
}