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
}
#[cfg(windows)]
pub fn home_dir() -> Option<PathBuf> {
use std::{
env,
ffi::{OsString, c_void},
os::windows::ffi::OsStringExt as _,
ptr, slice,
};
use windows_sys::Win32::{
Foundation::S_OK,
System::Com::CoTaskMemFree,
UI::Shell::{FOLDERID_Profile, KF_FLAG_DONT_VERIFY, SHGetKnownFolderPath},
};
#[cfg(not(target_vendor = "uwp"))]
fn home_dir_crt() -> Option<PathBuf> {
unsafe {
let mut path = ptr::null_mut();
match SHGetKnownFolderPath(
&FOLDERID_Profile,
KF_FLAG_DONT_VERIFY as u32,
ptr::null_mut(),
&mut path,
) {
S_OK => {
let path_slice = slice::from_raw_parts(path, wcslen(path));
let s = OsString::from_wide(&path_slice);
CoTaskMemFree(path.cast::<c_void>());
Some(PathBuf::from(s))
}
_ => {
CoTaskMemFree(path.cast::<c_void>());
None
}
}
}
}
#[cfg(target_vendor = "uwp")]
fn home_dir_crt() -> Option<PathBuf> {
None
}
extern "C" {
fn wcslen(buf: *const u16) -> usize;
}
env::var_os("USERPROFILE").filter(|s| !s.is_empty()).map(PathBuf::from).or_else(home_dir_crt)
}
#[cfg(not(windows))]
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);
}
}