use std::path::{Component, Path, PathBuf};
use anyhow::{Context, Result, bail};
#[derive(Debug, Clone, PartialEq, Eq)]
pub(super) enum Link {
Relative(String),
Absolute(String),
}
impl Link {
pub(super) fn text(&self) -> &str {
match self {
Self::Relative(path) | Self::Absolute(path) => path,
}
}
}
pub(super) fn link(from_dir: &Path, to: &Path) -> Result<Link> {
let from = absolute_lexical(from_dir)?;
let target = absolute_lexical(to)?;
let from_parts: Vec<Component<'_>> = from.components().collect();
let target_parts: Vec<Component<'_>> = target.components().collect();
let shared = from_parts
.iter()
.zip(&target_parts)
.take_while(|(left, right)| left == right)
.count();
let shares_a_directory = from_parts[..shared]
.iter()
.any(|component| matches!(component, Component::Normal(_)));
if !shares_a_directory {
return Ok(Link::Absolute(text(&target)?));
}
let mut parts: Vec<String> = vec!["..".to_owned(); from_parts.len() - shared];
for component in &target_parts[shared..] {
parts.push(component_text(*component, &target)?);
}
if parts.is_empty() {
return Ok(Link::Relative(".".to_owned()));
}
Ok(Link::Relative(parts.join("/")))
}
fn text(path: &Path) -> Result<String> {
path.to_str().map(str::to_owned).with_context(|| {
format!(
"`{}` is not valid UTF-8, so it cannot be written into the generated crate",
path.display()
)
})
}
fn component_text(component: Component<'_>, whole: &Path) -> Result<String> {
component
.as_os_str()
.to_str()
.map(str::to_owned)
.with_context(|| {
format!(
"`{}` is not valid UTF-8, so it cannot be written into the generated crate",
whole.display()
)
})
}
fn absolute_lexical(path: &Path) -> Result<PathBuf> {
let absolute = std::path::absolute(path).with_context(|| {
format!(
"could not resolve `{}` against the working directory",
path.display()
)
})?;
let mut normalised = PathBuf::new();
for component in absolute.components() {
match component {
Component::CurDir => {}
Component::ParentDir => {
if !normalised.pop() {
bail!("`{}` climbs above the filesystem root", absolute.display());
}
}
other => normalised.push(other.as_os_str()),
}
}
Ok(normalised)
}