use super::{Config, private_root};
use crate::workspace::vcs;
use std::path::{Path, PathBuf};
#[derive(Debug, PartialEq, Eq)]
pub enum ClonePath {
Outside,
Mapped(String),
Unmapped {
clone: PathBuf,
rel: String,
},
}
pub fn locate(cfg: &Config, abs: &Path) -> ClonePath {
match clone_of(cfg, abs) {
Some(clone) => locate_in(cfg, &clone, abs),
None => ClonePath::Outside,
}
}
pub fn clone_of(cfg: &Config, abs: &Path) -> Option<PathBuf> {
let clone = existing_ancestor(abs).and_then(|d| vcs::git_root(&d)).and_then(|c| c.canonicalize().ok())?;
let m = private_root::mapping(&clone)?;
(m.spec.canonicalize().ok() == cfg.spec_home.canonicalize().ok()).then_some(clone)
}
pub fn locate_in(cfg: &Config, clone: &Path, abs: &Path) -> ClonePath {
let Some(m) = private_root::mapping(clone) else { return ClonePath::Outside };
let Some(rel) = crate::paths::rel_of(clone, abs) else { return ClonePath::Outside };
let clone = clone.to_path_buf();
match public_path(cfg, &clone, m.public.as_deref()) {
Some(p) if rel.is_empty() => ClonePath::Mapped(p),
Some(p) => ClonePath::Mapped(format!("{p}/{rel}")),
None => ClonePath::Unmapped { clone, rel },
}
}
fn public_path(cfg: &Config, clone: &Path, named: Option<&str>) -> Option<String> {
let clean = |p: &str| p.trim_start_matches("./").trim_end_matches('/').to_string();
if let Some(p) = named {
return Some(clean(p));
}
let public = &cfg.raw.public;
let mut all: Vec<String> = public
.paths
.iter()
.chain(public.companion.keys())
.map(|p| clean(p))
.filter(|p| !p.is_empty() && !p.contains(['*', '?', '[', '{']))
.collect();
all.sort();
all.dedup();
if let [only] = all.as_slice() {
return Some(only.clone());
}
let name = clone.file_name()?.to_str()?;
let same: Vec<&String> = all.iter().filter(|p| p.rsplit('/').next() == Some(name)).collect();
match same.as_slice() {
[one] => Some((*one).clone()),
_ => None,
}
}
fn existing_ancestor(abs: &Path) -> Option<PathBuf> {
abs.ancestors().find(|a| a.is_dir()).map(Path::to_path_buf)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::types::RawConfig;
fn cfg(paths: &[&str]) -> Config {
let mut raw = RawConfig::default();
raw.public.paths = paths.iter().map(|p| p.to_string()).collect();
Config::from_raw(PathBuf::from("/r"), raw, false)
}
#[test]
fn public_path_takes_the_only_one_or_the_one_named_like_the_clone() {
let clone = Path::new("/w/cli");
assert_eq!(public_path(&cfg(&["packages/tool"]), clone, None).as_deref(), Some("packages/tool"));
assert_eq!(public_path(&cfg(&["libs/auth", "packages/cli/"]), clone, None).as_deref(), Some("packages/cli"));
assert_eq!(public_path(&cfg(&["libs/auth", "libs/shop"]), clone, None), None);
assert_eq!(public_path(&cfg(&[]), clone, None), None);
assert_eq!(
public_path(&cfg(&["libs/auth", "libs/shop"]), clone, Some("libs/shop")).as_deref(),
Some("libs/shop")
);
}
}