use crate::prompt::{AdapterRunner, Sleeper, brazen_pin};
use crate::template::GitRunner;
use std::cell::RefCell;
use std::collections::VecDeque;
use std::ffi::OsString;
use std::io;
use std::path::{Path, PathBuf};
use std::time::Duration;
pub(super) enum AdapterReply {
Ok(Vec<u8>),
Err(io::Error),
}
pub(super) type AdapterCall = (OsString, Vec<String>, Vec<u8>);
pub(super) fn version_line() -> Vec<u8> {
format!("bz {}\n", brazen_pin()).into_bytes()
}
pub(super) struct StubAdapter {
replies: RefCell<VecDeque<AdapterReply>>,
stderr: RefCell<VecDeque<Vec<u8>>>,
pub(super) observed: RefCell<Vec<AdapterCall>>,
}
impl StubAdapter {
pub(super) fn scripted<I>(replies: I) -> Self
where
I: IntoIterator<Item = AdapterReply>,
{
Self {
replies: RefCell::new(replies.into_iter().collect()),
stderr: RefCell::new(VecDeque::new()),
observed: RefCell::new(Vec::new()),
}
}
pub(super) fn happy(model_stream: &[u8]) -> Self {
Self::scripted([
AdapterReply::Ok(version_line()),
AdapterReply::Ok(model_stream.to_vec()),
])
}
pub(super) fn happy_with_stderr(model_stream: &[u8], stderr: &[u8]) -> Self {
let stub = Self::happy(model_stream);
*stub.stderr.borrow_mut() = [Vec::new(), stderr.to_vec()].into();
stub
}
pub(super) fn reply_ok(bytes: &[u8]) -> AdapterReply {
AdapterReply::Ok(bytes.to_vec())
}
pub(super) fn reply_err(kind: io::ErrorKind, msg: &str) -> AdapterReply {
AdapterReply::Err(io::Error::new(kind, msg.to_string()))
}
}
impl AdapterRunner for StubAdapter {
fn run(
&self,
binary: &OsString,
args: &[&str],
stdin_bytes: &[u8],
on_line: &mut dyn FnMut(&[u8]) -> io::Result<()>,
) -> io::Result<Vec<u8>> {
self.observed.borrow_mut().push((
binary.clone(),
args.iter().map(|s| (*s).to_owned()).collect(),
stdin_bytes.to_vec(),
));
let bytes = match self.replies.borrow_mut().pop_front() {
Some(AdapterReply::Ok(b)) => b,
Some(AdapterReply::Err(e)) => return Err(e),
None => panic!("StubAdapter::run called more times than scripted"),
};
for line in bytes.split(|b| *b == b'\n') {
if line.is_empty() {
continue;
}
on_line(line)?;
}
Ok(self.stderr.borrow_mut().pop_front().unwrap_or_default())
}
}
pub(super) fn unreachable_adapter() -> StubAdapter {
StubAdapter::scripted([])
}
#[derive(Default)]
pub(super) struct StubSleeper {
pub(super) slept: RefCell<Vec<Duration>>,
}
impl Sleeper for StubSleeper {
fn sleep(&self, dur: Duration) {
self.slept.borrow_mut().push(dur);
}
}
pub(super) const STUB_SHA: &str = "cafecafecafecafecafecafecafecafecafecafe";
#[derive(Default)]
pub(super) struct StubGit {
pub(super) runs: RefCell<Vec<(PathBuf, Vec<String>)>>,
fail_at: Option<usize>,
}
impl StubGit {
pub(super) fn ok() -> Self {
Self::default()
}
pub(super) fn failing_at(idx: usize) -> Self {
Self {
fail_at: Some(idx),
..Self::default()
}
}
}
impl GitRunner for StubGit {
fn run(&self, dest: &Path, args: &[&str]) -> io::Result<()> {
let mut runs = self.runs.borrow_mut();
let idx = runs.len();
runs.push((
dest.to_path_buf(),
args.iter().map(|s| (*s).to_owned()).collect(),
));
if self.fail_at == Some(idx) {
Err(io::Error::other(format!("stub git fail at {idx}")))
} else {
Ok(())
}
}
fn run_capture(&self, dest: &Path, args: &[&str]) -> io::Result<String> {
self.run(dest, args)?;
match args.first().copied() {
Some("show") => {
let spec = args.last().unwrap_or(&"");
let (_, path) = spec
.split_once(':')
.ok_or_else(|| io::Error::other("stub show: no <sha>:<path> spec"))?;
let ws = dest.parent().expect("dest is <workspace>/repo.git");
std::fs::read_to_string(ws.join(path))
}
Some("rev-parse" | "merge-base") => Ok(STUB_SHA.to_string()),
Some("for-each-ref") => Ok("refs/heads/config/default".to_string()),
_ => Ok(String::new()),
}
}
}