use std::env;
use std::hash::{DefaultHasher, Hash, Hasher};
use std::path::{Component, Path, PathBuf, is_separator};
use camino::{Utf8Component, Utf8Path, Utf8PathBuf};
use digest::Digest;
use crate::Error;
pub fn bounded_jobs(jobs: usize) -> usize {
let cpus = num_cpus::get();
if jobs > 0 && jobs <= cpus { jobs } else { cpus }
}
pub fn bounded_thread_pool(threads: usize) {
let threads = bounded_jobs(threads);
if threads != num_cpus::get() {
rayon::ThreadPoolBuilder::new()
.num_threads(threads)
.build_global()
.unwrap_or_else(|e| panic!("failed creating thread pool: {e}"));
}
}
pub fn hash<T: Hash>(obj: T) -> u64 {
let mut hasher = DefaultHasher::new();
obj.hash(&mut hasher);
hasher.finish()
}
pub(crate) fn digest<D: Digest>(data: &[u8]) -> String {
let mut hasher = D::new();
hasher.update(data);
hex::encode(hasher.finalize())
}
pub fn current_dir() -> crate::Result<Utf8PathBuf> {
let dir = env::current_dir()
.map_err(|e| Error::InvalidValue(format!("can't get current dir: {e}")))?;
Utf8PathBuf::try_from(dir)
.map_err(|e| Error::InvalidValue(format!("invalid unicode path: {e}")))
}
pub(crate) fn is_single_component<S: AsRef<str>>(path: S) -> bool {
!path.as_ref().contains(is_separator)
}
pub fn relpath<P, B>(path: P, base: B) -> Option<PathBuf>
where
P: AsRef<Path>,
B: AsRef<Path>,
{
let path = path.as_ref();
let base = base.as_ref();
if path.is_absolute() != base.is_absolute() {
if path.is_absolute() {
Some(PathBuf::from(path))
} else {
None
}
} else {
let mut ita = path.components();
let mut itb = base.components();
let mut comps: Vec<Component> = vec![];
loop {
match (ita.next(), itb.next()) {
(None, None) => break,
(Some(a), None) => {
comps.push(a);
comps.extend(ita);
break;
}
(None, _) => comps.push(Component::ParentDir),
(Some(a), Some(b)) if comps.is_empty() && a == b => (),
(Some(a), Some(Component::CurDir)) => comps.push(a),
(Some(_), Some(Component::ParentDir)) => return None,
(Some(a), Some(_)) => {
comps.push(Component::ParentDir);
comps.extend(itb.map(|_| Component::ParentDir));
comps.push(a);
comps.extend(ita);
break;
}
}
}
Some(comps.iter().collect())
}
}
pub fn relpath_utf8<P, B>(path: P, base: B) -> Option<Utf8PathBuf>
where
P: AsRef<Utf8Path>,
B: AsRef<Utf8Path>,
{
let path = path.as_ref();
let base = base.as_ref();
if path.is_absolute() != base.is_absolute() {
if path.is_absolute() {
Some(Utf8PathBuf::from(path))
} else {
None
}
} else {
let mut ita = path.components();
let mut itb = base.components();
let mut comps: Vec<Utf8Component> = vec![];
loop {
match (ita.next(), itb.next()) {
(None, None) => break,
(Some(a), None) => {
comps.push(a);
comps.extend(ita);
break;
}
(None, _) => comps.push(Utf8Component::ParentDir),
(Some(a), Some(b)) if comps.is_empty() && a == b => (),
(Some(a), Some(Utf8Component::CurDir)) => comps.push(a),
(Some(_), Some(Utf8Component::ParentDir)) => return None,
(Some(a), Some(_)) => {
comps.push(Utf8Component::ParentDir);
comps.extend(itb.map(|_| Utf8Component::ParentDir));
comps.push(a);
comps.extend(ita);
break;
}
}
}
Some(comps.iter().collect())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn relpaths() {
for (path, base, expected) in [
("path", "path", Some("")),
("/path", "path", Some("/path")),
("path", "/path", None),
("/path", "/path", Some("")),
("", "", Some("")),
("", ".", Some("..")),
("", "..", Some("..")),
("/", "", Some("/")),
("", "/", None),
("/", "path", Some("/")),
("path/file", "./path", Some("path/../file")),
("path/file", "path/../file", None),
("path", "/", None),
("/path/to/file", "/path/to", Some("file")),
("/path/to/file", "/path/to/", Some("file")),
] {
let relpath_utf8 = relpath_utf8(path, base).map(|x| x.to_string());
let relpath = relpath(path, base).map(|x| x.to_str().unwrap().to_string());
for value in [relpath_utf8, relpath] {
assert_eq!(value.as_deref(), expected, "path {path:?}, base {base:?}");
}
}
}
}