use std::ffi::OsStr;
use std::ffi::OsString;
use std::path::Path;
use std::path::PathBuf;
use std::path::is_separator;
pub fn resolve(var: impl Fn(&str) -> Option<OsString>, home: Option<&Path>) -> PathBuf {
let expand = |value: OsString| vars(&user(value, home), &var);
let tilde = home.map_or_else(|| PathBuf::from("~"), Path::to_path_buf);
let xdg = var("XDG_CACHE_HOME").map_or_else(|| tilde.join(".cache"), PathBuf::from);
let hf_home = expand(var("HF_HOME").unwrap_or_else(|| xdg.join("huggingface").into()));
let legacy =
var("HUGGINGFACE_HUB_CACHE").unwrap_or_else(|| Path::new(&hf_home).join("hub").into());
PathBuf::from(expand(var("HF_HUB_CACHE").unwrap_or(legacy)))
}
fn user(value: OsString, home: Option<&Path>) -> OsString {
let (Some(text), Some(home)) = (value.to_str(), home.and_then(Path::to_str)) else {
return value;
};
match text.strip_prefix('~') {
Some(rest) if rest.is_empty() || rest.starts_with(is_separator) => {
let joined = format!("{}{rest}", home.trim_end_matches(is_separator));
OsString::from(if joined.is_empty() { "/" } else { &joined })
}
_ => value,
}
}
fn vars(value: &OsStr, var: &impl Fn(&str) -> Option<OsString>) -> OsString {
let Some(mut rest) = value.to_str() else {
return value.to_os_string();
};
let mut out = OsString::new();
while let Some(at) = rest.find('$') {
out.push(&rest[..at]);
let after = &rest[at + 1..];
let (name, len) = match after.strip_prefix('{') {
Some(braced) => braced
.find('}')
.map_or(("", 0), |end| (&braced[..end], end + 2)),
None => {
let word = |c: char| c.is_ascii_alphanumeric() || c == '_';
let end = after.find(|c: char| !word(c)).unwrap_or(after.len());
(&after[..end], end)
}
};
let set = (!name.is_empty() && !name.contains(['=', '\0']))
.then(|| var(name))
.flatten();
match (len, set) {
(0, _) => out.push("$"),
(_, Some(found)) => out.push(found),
(_, None) => out.push(&rest[at..at + 1 + len]),
}
rest = &after[len..];
}
out.push(rest);
out
}