use serde::{Deserialize, Serialize};
use std::io::{self, Read, Write};
use std::path::{Path, PathBuf};
use thiserror::Error;
use super::super::{ENV_CONV_BRANCH, ENV_CONV_REPO};
use super::dispatch::EnvLookup;
use crate::template::{GitRunner, RealGit};
use crate::workspace;
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
struct Input {
path: String,
}
#[derive(Debug, Serialize, PartialEq, Eq)]
struct Output {
cwd: String,
}
#[derive(Debug, Error)]
pub enum Error {
#[error("invalid input JSON: {0}")]
InvalidJson(#[source] serde_json::Error),
#[error("read input from stdin: {0}")]
StdinRead(#[source] io::Error),
#[error("missing env var {0:?} (set by the harness per ARCH §3.3)")]
MissingEnv(&'static str),
#[error("no such directory {path:?}: {source}")]
NoSuchDir {
path: String,
#[source]
source: io::Error,
},
#[error("{path:?} is not a directory — cd takes a directory (ARCH §3.3)")]
NotADir { path: String },
#[error("store the working directory: {0}")]
Mark(#[source] io::Error),
#[error("write to stdout: {0}")]
Write(#[source] io::Error),
}
pub fn run<R: Read, W: Write>(
stdin: &mut R,
stdout: &mut W,
env: &dyn EnvLookup,
) -> Result<(), Error> {
run_with(stdin, stdout, env, &RealGit::new())
}
pub fn run_with<R: Read, W: Write>(
stdin: &mut R,
stdout: &mut W,
env: &dyn EnvLookup,
git: &dyn GitRunner,
) -> Result<(), Error> {
let mut buf = Vec::new();
stdin.read_to_end(&mut buf).map_err(Error::StdinRead)?;
let input: Input = serde_json::from_slice(&buf).map_err(Error::InvalidJson)?;
let repo = env
.get(ENV_CONV_REPO)
.ok_or(Error::MissingEnv(ENV_CONV_REPO))?;
let branch = env
.get(ENV_CONV_BRANCH)
.and_then(|v| v.into_string().ok())
.ok_or(Error::MissingEnv(ENV_CONV_BRANCH))?;
let target = resolve(&input.path)?;
let workspace = PathBuf::from(repo);
workspace::cwd::write(&workspace, &branch, &target, git).map_err(Error::Mark)?;
emit(stdout, &target)
}
fn resolve(path: &str) -> Result<PathBuf, Error> {
let abs = std::fs::canonicalize(path).map_err(|source| Error::NoSuchDir {
path: path.to_owned(),
source,
})?;
match abs.is_dir() {
true => Ok(abs),
false => Err(Error::NotADir {
path: path.to_owned(),
}),
}
}
fn emit<W: Write>(stdout: &mut W, dir: &Path) -> Result<(), Error> {
let payload = Output {
cwd: dir.to_string_lossy().into_owned(),
};
let bytes = serde_json::to_vec(&payload).expect("Output is always serializable");
stdout.write_all(&bytes).map_err(Error::Write)
}
#[cfg(test)]
mod tests;