use core::ops;
use std::path::{Path, PathBuf};
fn config_path(path: &Path) -> Option<PathBuf> {
let config = path.join("config");
if config.exists() {
return Some(config);
}
let config = path.join("config.toml");
if config.exists() {
return Some(config);
}
None
}
pub fn home_dir() -> Option<PathBuf> {
#[allow(deprecated)]
std::env::home_dir()
}
pub fn cargo_home_with_cwd(cwd: &Path) -> Option<PathBuf> {
match std::env::var_os("CARGO_HOME").filter(|h| !h.is_empty()).map(PathBuf::from) {
Some(home) => {
if home.is_absolute() {
Some(home)
} else {
Some(cwd.join(home))
}
}
_ => Some(home_dir()?.join(".cargo")),
}
}
pub fn rustup_home_with_cwd(cwd: &Path) -> Option<PathBuf> {
match std::env::var_os("RUSTUP_HOME").filter(|h| !h.is_empty()).map(PathBuf::from) {
Some(home) => {
if home.is_absolute() {
Some(home)
} else {
Some(cwd.join(home))
}
}
_ => Some(home_dir()?.join(".rustup")),
}
}
#[derive(Debug)]
#[must_use = "iterators are lazy and do nothing unless consumed"]
pub(crate) struct WalkInner<'a, P> {
ancestors: std::path::Ancestors<'a>,
cargo_home: Option<P>,
}
impl<'a, P: ops::Deref<Target = Path>> WalkInner<'a, P> {
pub(crate) fn with_cargo_home(current_dir: &'a Path, cargo_home: Option<P>) -> Self {
Self { ancestors: current_dir.ancestors(), cargo_home }
}
}
impl<P: ops::Deref<Target = Path>> Iterator for WalkInner<'_, P> {
type Item = PathBuf;
fn next(&mut self) -> Option<Self::Item> {
for p in self.ancestors.by_ref() {
let p = p.join(".cargo");
if self.cargo_home.as_deref() == Some(&p) {
self.cargo_home = None;
}
if let Some(p) = config_path(&p) {
return Some(p);
}
}
config_path(&self.cargo_home.take()?)
}
}
#[derive(Debug)]
#[must_use = "iterators are lazy and do nothing unless consumed"]
pub struct Walk<'a>(WalkInner<'a, PathBuf>);
impl<'a> Walk<'a> {
pub fn new(current_dir: &'a Path) -> Self {
Self::with_cargo_home(current_dir, cargo_home_with_cwd(current_dir))
}
pub fn with_cargo_home(current_dir: &'a Path, cargo_home: Option<PathBuf>) -> Self {
Self(WalkInner::with_cargo_home(current_dir, cargo_home))
}
}
impl Iterator for Walk<'_> {
type Item = PathBuf;
fn next(&mut self) -> Option<Self::Item> {
self.0.next()
}
}
#[cfg(test)]
mod tests {
use fs_err as fs;
use super::*;
#[test]
fn walk() {
let tmp = tempfile::tempdir().unwrap();
let p = tmp.path();
let home = &p.join("a/.cargo");
let cwd = &p.join("a/b/c");
fs::create_dir_all(home).unwrap();
fs::write(p.join("a/.cargo/config"), "").unwrap();
fs::create_dir_all(p.join("a/b/.cargo")).unwrap();
fs::write(p.join("a/b/.cargo/config"), "").unwrap();
fs::write(p.join("a/b/.cargo/config.toml"), "").unwrap();
fs::create_dir_all(p.join("a/b/c/.cargo")).unwrap();
fs::write(p.join("a/b/c/.cargo/config.toml"), "").unwrap();
let mut w = Walk::with_cargo_home(cwd, Some(home.clone()));
assert_eq!(w.next(), Some(p.join("a/b/c/.cargo/config.toml")));
assert_eq!(w.next(), Some(p.join("a/b/.cargo/config")));
assert_eq!(w.next(), Some(p.join("a/.cargo/config")));
assert_eq!(w.next(), None);
}
}