use std::path::{Component, Path, PathBuf};
use crate::error::Error;
pub fn targets(shep_home: &Path) -> Result<Vec<String>, Error> {
let root = shep_home.join("deploy");
let entries = match std::fs::read_dir(&root) {
Ok(entries) => entries,
Err(source) if source.kind() == std::io::ErrorKind::NotFound => return Ok(Vec::new()),
Err(source) => return Err(Error::Io { path: root, source }),
};
let mut found = Vec::new();
for entry in entries {
let entry = entry.map_err(|source| Error::Io {
path: root.clone(),
source,
})?;
if !entry.path().join("deploy.toml").is_file() {
continue;
}
if let Some(name) = entry.file_name().to_str() {
found.push(name.to_owned());
}
}
found.sort();
Ok(found)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Tree {
root: PathBuf,
sheep: String,
}
#[must_use]
pub fn is_sheep_name(sheep: &str) -> bool {
let mut components = Path::new(sheep).components();
let one_ordinary_component =
matches!(components.next(), Some(Component::Normal(only)) if only == sheep);
one_ordinary_component && components.next().is_none()
}
impl Tree {
#[must_use]
pub fn for_sheep(shep_home: &Path, sheep: &str) -> Self {
Self {
root: shep_home.join("deploy").join(sheep),
sheep: sheep.to_owned(),
}
}
#[must_use]
pub fn sheep(&self) -> &str {
&self.sheep
}
#[must_use]
pub fn root(&self) -> &Path {
&self.root
}
#[must_use]
pub fn git(&self) -> PathBuf {
self.root.join("git")
}
#[must_use]
pub fn releases(&self) -> PathBuf {
self.root.join("releases")
}
#[must_use]
pub fn release(&self, sha: &str) -> PathBuf {
self.releases().join(sha)
}
#[must_use]
pub fn current(&self) -> PathBuf {
self.root.join("current")
}
#[must_use]
pub fn completion(&self, sha: &str) -> PathBuf {
self.root.join("complete").join(sha)
}
#[must_use]
pub fn lock_file(&self) -> PathBuf {
self.root.join("deploy.lock")
}
#[must_use]
pub fn state_file(&self) -> PathBuf {
self.root.join("deploy.toml")
}
#[must_use]
pub fn cache(&self) -> PathBuf {
self.root.join("cache")
}
#[must_use]
pub fn cache_target(&self) -> PathBuf {
self.cache().join("target")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn every_path_matches_the_documented_layout() {
let tree = Tree::for_sheep(Path::new("/srv/shep"), "bpm");
assert_eq!(tree.git(), Path::new("/srv/shep/deploy/bpm/git"));
assert_eq!(tree.releases(), Path::new("/srv/shep/deploy/bpm/releases"));
assert_eq!(
tree.release("a1b2c3d"),
Path::new("/srv/shep/deploy/bpm/releases/a1b2c3d")
);
assert_eq!(tree.current(), Path::new("/srv/shep/deploy/bpm/current"));
assert_eq!(
tree.state_file(),
Path::new("/srv/shep/deploy/bpm/deploy.toml")
);
}
#[test]
fn a_tree_knows_its_own_sheep() {
assert_eq!(
Tree::for_sheep(Path::new("/srv/shep"), "bpm").sheep(),
"bpm"
);
}
#[test]
fn different_sheep_get_different_trees() {
let a = Tree::for_sheep(Path::new("/srv/shep"), "bpm");
let b = Tree::for_sheep(Path::new("/srv/shep"), "ctm");
assert_ne!(a.state_file(), b.state_file());
}
#[test]
fn the_build_cache_lives_in_the_tree() {
let tree = Tree::for_sheep(Path::new("/srv/shep"), "koji");
assert_eq!(tree.cache(), Path::new("/srv/shep/deploy/koji/cache"));
assert_eq!(
tree.cache_target(),
Path::new("/srv/shep/deploy/koji/cache/target")
);
}
#[test]
fn the_cache_is_not_inside_releases() {
let tree = Tree::for_sheep(Path::new("/srv/shep"), "koji");
assert!(!tree.cache().starts_with(tree.releases()));
}
}