use std::{borrow::Cow, env, ops::ControlFlow, path::PathBuf};
use crate::{
EnvPath, OsCow,
os_cow::{self, into_os_cow},
};
impl EnvPath<'_> {
#[cfg(windows)] pub(crate) fn get_microsoft_windows_data_dir() -> Option<PathBuf> {
dirs::data_local_dir().map(|x| x.join("Microsoft")) }
pub(crate) fn set_bin_dir<'a>() -> OsCow<'a> {
let bin_dir = || dirs::data_local_dir().and_then(|p| into_os_cow(p.join("bin")));
match dirs::executable_dir() {
Some(s) => into_os_cow(s),
#[cfg(windows)]
_ => match Self::get_microsoft_windows_data_dir() {
Some(x) => into_os_cow(x.join("WindowsApps")),
_ => bin_dir(),
},
#[cfg(unix)]
_ => match dirs::home_dir() {
Some(x) => into_os_cow(x.join(".local/bin")),
_ => bin_dir(),
},
#[cfg(not(any(unix, windows)))]
_ => bin_dir(),
}
}
pub(crate) fn set_font_dir<'a>() -> OsCow<'a> {
match dirs::font_dir() {
Some(s) => into_os_cow(s),
#[cfg(windows)]
_ => match Self::get_microsoft_windows_data_dir() {
Some(x) => into_os_cow(x.join(r#"Windows\Fonts"#)),
_ => os_cow::from_str(r#"C:\Windows\Fonts"#),
},
#[cfg(unix)]
_ => dirs::data_dir().and_then(|p| into_os_cow(p.join("fonts"))),
#[cfg(not(any(unix, windows)))]
_ => None, }
}
pub(crate) fn set_double_ended_path(s: &str) -> OsCow {
let Some(path) = env::var_os("PATH") else {
return None; };
let path_iter = || env::split_paths(&path); let into_os_cow = |x: PathBuf| Cow::from(x.into_os_string());
match s {
"first" => path_iter()
.next()
.map(into_os_cow),
"last" => path_iter()
.last()
.map(into_os_cow),
_ => None, }
}
pub(crate) fn set_dir<F>(p: F, _android_dir: &str) -> OsCow
where
F: FnOnce() -> Option<PathBuf>,
{
match () {
#[cfg(target_os = "android")]
() => os_cow::set_android_dir(_android_dir),
#[allow(unreachable_patterns)]
() => p().and_then(into_os_cow),
}
}
pub(crate) fn handle_dirs(ident: &str) -> OsCow {
use ControlFlow::{Break, Continue};
match Self::get_question_mark_separator(ident) {
' ' => Self::match_base_dirs(ident),
sep => match Self::parse_dir_rules(ident, Self::match_base_dirs, sep) {
Break(x) | Continue(x) => x,
},
}
}
pub(crate) fn match_base_dirs(ident: &str) -> OsCow {
use dirs::*;
let into_cow = |p: Option<PathBuf>| p.and_then(into_os_cow);
match ident {
"music" | "audio" => Self::set_dir(audio_dir, "Music"),
"cache" => into_cow(cache_dir()),
"cfg" | "config" => into_cow(config_dir()),
"data" => into_cow(data_dir()),
"local_data" | "local-data" => Self::set_dir(data_local_dir, "Android/data"),
"local-cfg" | "local_cfg" | "local_config" => {
Self::set_dir(config_local_dir, "Android/data")
}
"desktop" => into_cow(desktop_dir()),
"doc" | "document" | "documentation" => {
Self::set_dir(document_dir, "Documents")
}
"dl" | "download" => Self::set_dir(download_dir, "Download"),
"bin" | "exe" | "executable" => Self::set_bin_dir(),
"path" | "first-path" | "first_path" => Self::set_double_ended_path("first"),
"last_path" | "last-path" => Self::set_double_ended_path("last"),
"font" | "typeface" => Self::set_font_dir(),
"home" => into_cow(home_dir()),
"pic" | "picture" => Self::set_dir(audio_dir, "Pictures"),
"pref" | "preference" => into_cow(preference_dir()),
"pub" | "public" => into_cow(public_dir()),
"runtime" => into_cow(runtime_dir()),
"state" => into_cow(state_dir()),
"template" => into_cow(template_dir()),
"video" | "movie" => Self::set_dir(video_dir, "Movies"),
"tmp" => into_os_cow(get_tmp_dir()),
#[cfg(feature = "rand")]
"tmp-rand" | "tmp_random" => into_os_cow(get_tmp_random_dir(None, None)),
"temp" | "temporary" => into_os_cow(env::temp_dir()),
#[cfg(target_os = "android")]
"sd" => os_cow::from_str(os_cow::AND_SD),
#[cfg(windows)]
"local-low" | "local_low" => into_cow(data_local_dir().and_then(|p| {
p.parent()
.map(|x| x.join("LocalLow"))
})),
"cli-data" | "cli_data" => into_cow(data_local_dir()),
"cli-cfg" | "cli_cfg" | "cli_config" => into_cow(config_local_dir()),
"cli-cache" | "cli_cache" => into_cow(cache_dir()),
#[cfg(windows)]
"progam-files" | "program_files" => Self::into_os_env("ProgramFiles")
.or_else(|| os_cow::from_str(r#"C:\Program Files"#)),
#[cfg(windows)]
"program-files-x86" | "program_files_x86" => {
Self::into_os_env("ProgramFiles(x86)")
.or_else(|| os_cow::from_str(r#"=C:\Program Files (x86)"#))
}
#[cfg(windows)]
"common-program-files" | "common_program_files" => {
Self::into_os_env("CommonProgramFiles")
.or_else(|| os_cow::from_str(r#"C:\Program Files\Common Files"#))
}
#[cfg(windows)]
"common-program-files-x86" | "common_program_files_x86" => {
Self::into_os_env("CommonProgramFiles(x86)")
.or_else(|| os_cow::from_str(r#"C:\Program Files (x86)\Common Files"#))
}
#[cfg(windows)]
"program-data" | "program_data" => Self::into_os_env("ProgramData")
.or_else(|| os_cow::from_str(r#"C:\ProgramData"#)),
#[cfg(windows)]
"microsoft" => into_cow(data_dir().map(|x| x.join("Microsoft"))),
"empty" => os_cow::from_str(""),
x if Self::starts_with_remix_expr(x) => Self::parse_remix_expr(x),
_ => None,
}
}
}
pub fn get_tmp_dir() -> PathBuf {
match env::var_os("TMPDIR") {
Some(s) => PathBuf::from(s),
None => match env::temp_dir() {
p if p
.metadata()
.map_or(true, |x| x.permissions().readonly()) =>
{
dirs::cache_dir()
.map_or_else(|| PathBuf::from_iter([".tmp"]), |x| x.join("tmp"))
}
p => p,
},
}
}
#[cfg(feature = "rand")]
pub fn get_tmp_random_dir(
prefix: Option<&str>,
rand_length: Option<usize>,
) -> PathBuf {
let random = crate::random::get_random_value(rand_length);
let join_random = |s| get_tmp_dir().join(s);
match prefix {
Some(x) if x.trim().is_empty() => join_random(random),
Some(x) => join_random(format!("{x}{random}")),
_ => join_random(random),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::EnvPath;
#[test]
fn remix_dir() {
let p = EnvPath::new(["$env: user ?? dir * cfg ? empty"]);
dbg!(p);
let p2 = EnvPath::new(["$dir: runtimes ?? test ? env * HOME"]);
dbg!(p2);
}
#[test]
#[cfg(feature = "rand")]
fn random_tmp_dir() {
use rand::{Rng, distr::Alphanumeric};
let random = rand::rng()
.sample_iter(&Alphanumeric)
.take(16)
.map(char::from)
.collect::<String>();
let dir = get_tmp_dir().join(random);
dbg!(&dir);
}
#[test]
#[cfg(feature = "rand")]
fn get_random_tmp_dir() {
let dir = get_tmp_random_dir(None, None);
dbg!(&dir);
}
}