use std::path::{Path, PathBuf};
use strop_workspace::{RemoteEndpoint, RemoteFile};
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum RepoTarget {
Local {
#[serde(with = "strop_core::path_serde")]
workdir: PathBuf,
},
Remote {
endpoint: RemoteEndpoint,
#[serde(with = "strop_core::path_serde")]
workdir: PathBuf,
},
}
impl RepoTarget {
pub fn workdir(&self) -> &Path {
match self {
Self::Local { workdir } | Self::Remote { workdir, .. } => workdir,
}
}
pub fn remote_file(&self, rel: &Path) -> Option<RemoteFile> {
match self {
Self::Local { .. } => None,
Self::Remote { endpoint, workdir } => {
RemoteFile::from_path(endpoint.clone(), workdir.join(rel)).ok()
}
}
}
pub fn is_remote(&self) -> bool {
matches!(self, Self::Remote { .. })
}
pub fn rel_of(&self, path: &Path) -> Option<PathBuf> {
path.strip_prefix(self.workdir())
.ok()
.map(|rel| rel.to_path_buf())
}
pub fn abs_of(&self, rel: &Path) -> PathBuf {
self.workdir().join(rel)
}
pub fn endpoint(&self) -> Option<&RemoteEndpoint> {
match self {
Self::Local { .. } => None,
Self::Remote { endpoint, .. } => Some(endpoint),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn remote_target() -> RepoTarget {
RepoTarget::Remote {
endpoint: RemoteEndpoint::parse("ssh://fixture@box.example:2222").unwrap(),
workdir: PathBuf::from("/srv/proj"),
}
}
#[test]
fn remote_identity_is_endpoint_plus_workdir() {
let a = remote_target();
let same = RepoTarget::Remote {
endpoint: RemoteEndpoint::parse("ssh://fixture@box.example:2222").unwrap(),
workdir: PathBuf::from("/srv/proj"),
};
let other_port = RepoTarget::Remote {
endpoint: RemoteEndpoint::parse("ssh://fixture@box.example:2223").unwrap(),
workdir: PathBuf::from("/srv/proj"),
};
let other_dir = RepoTarget::Remote {
endpoint: RemoteEndpoint::parse("ssh://fixture@box.example:2222").unwrap(),
workdir: PathBuf::from("/other"),
};
assert_eq!(a, same);
assert_ne!(a, other_port);
assert_ne!(a, other_dir);
assert_ne!(
a,
RepoTarget::Local {
workdir: PathBuf::from("/srv/proj")
}
);
}
#[test]
fn rel_of_strips_the_owning_workdir() {
let target = remote_target();
assert_eq!(
target.rel_of(Path::new("/srv/proj/src/main.rs")),
Some(PathBuf::from("src/main.rs"))
);
assert_eq!(target.rel_of(Path::new("/home/me/src/main.rs")), None);
}
#[test]
fn remote_file_carries_endpoint_and_native_path() {
let target = remote_target();
let file = target.remote_file(Path::new("src/a b.rs")).unwrap();
assert_eq!(
file.endpoint(),
&RemoteEndpoint::parse("ssh://fixture@box.example:2222").unwrap()
);
assert_eq!(file.path(), Path::new("/srv/proj/src/a b.rs"));
assert!(target.remote_file(Path::new("x")).is_some());
}
#[test]
fn local_target_has_no_remote_identity() {
let target = RepoTarget::Local {
workdir: PathBuf::from("/w"),
};
assert_eq!(target.endpoint(), None);
assert!(!target.is_remote());
assert_eq!(target.remote_file(Path::new("a.rs")), None);
}
#[test]
fn serde_round_trips_remote_provenance() {
let target = remote_target();
let text = serde_json::to_string(&target).unwrap();
let back: RepoTarget = serde_json::from_str(&text).unwrap();
assert_eq!(target, back);
}
}