use anyhow::Context as _;
use anyhow::{bail, Ok, Result};
use dirs::{config_dir, config_local_dir, desktop_dir, document_dir};
use std::path::PathBuf;
fn get_known_folder(s: &str) -> Result<String> {
let mut path = PathBuf::new();
match s {
"FOLDERID_Desktop" => {
path.push(desktop_dir().unwrap());
}
"FOLDERID_Documents" => {
path.push(document_dir().unwrap());
}
"FOLDERID_LocalAppData" => {
path.push(config_local_dir().unwrap());
}
"FOLDERID_RoamingAppData" => {
path.push(config_dir().unwrap());
}
_ => bail!("invalid KNOWNFOLDERID: {}", s),
}
Ok(path.to_str().unwrap().to_string())
}
fn is_known_folder_id(s: &str) -> bool {
matches!(
s,
"FOLDERID_Desktop"
| "FOLDERID_Documents"
| "FOLDERID_LocalAppData"
| "FOLDERID_RoamingAppData"
)
}
pub fn expand_env_var(s: &str) -> Result<String> {
let mut result = String::new();
let mut chars = s.chars();
#[allow(clippy::while_let_on_iterator)]
while let Some(c) = chars.next() {
if c == '%' {
let mut varname = String::new();
let mut is_end = false;
while let Some(c) = chars.next() {
if c == '%' {
is_end = true;
break;
}
varname.push(c);
}
if !is_end {
bail!("invalid environment variable: {}", s);
}
if varname.is_empty() {
bail!("invalid environment variable: {}", s);
}
if is_known_folder_id(&varname) {
result.push_str(&get_known_folder(&varname)?);
continue;
}
let value = std::env::var(&varname)
.with_context(|| format!("env var {} not found", varname))?;
result.push_str(&value);
} else {
result.push(c);
}
}
Ok(result)
}
#[cfg(test)]
mod tests {
use super::*;
use rstest::rstest;
#[rstest]
#[case("FOLDERID_Desktop", true)]
#[case("FOLDERID_Documents", true)]
#[case("FOLDERID_LocalAppData", true)]
#[case("FOLDERID_RoamingAppData", true)]
#[case("FOLDERID_Desktop2", false)]
fn test_is_known_folder_id(#[case] input: &str, #[case] expected: bool) {
assert_eq!(is_known_folder_id(input), expected);
}
#[rstest]
#[case("FOLDERID_Desktop", desktop_dir().unwrap().to_str().unwrap().to_string())]
#[case("FOLDERID_Documents", document_dir().unwrap().to_str().unwrap().to_string())]
#[case(
"FOLDERID_LocalAppData",
config_local_dir().unwrap().to_str().unwrap().to_string()
)]
#[case(
"FOLDERID_RoamingAppData",
config_dir().unwrap().to_str().unwrap().to_string()
)]
fn test_get_known_folder(#[case] input: &str, #[case] expected: String) {
assert_eq!(get_known_folder(input).unwrap(), expected);
}
#[rstest]
#[case("FOLDERID_Desktop2")]
fn test_get_known_folder_error(#[case] input: &str) {
assert!(get_known_folder(input).is_err());
}
#[rstest]
#[case("%FOLDERID_Desktop%", desktop_dir().unwrap().to_str().unwrap().to_string())]
#[case(
"%FOLDERID_Documents%",
document_dir().unwrap().to_str().unwrap().to_string()
)]
#[case(
"%FOLDERID_LocalAppData%",
config_local_dir().unwrap().to_str().unwrap().to_string()
)]
#[case(
"%FOLDERID_RoamingAppData%",
config_dir().unwrap().to_str().unwrap().to_string()
)]
#[case("%USERPROFILE%", dirs::home_dir().unwrap().to_str().unwrap().to_string())]
#[case("%USERPROFILE%\\foo", format!("{}\\foo", dirs::home_dir().unwrap().to_str().unwrap()))]
#[case("%USERPROFILE%\\foo\\%USERNAME%", format!("{}\\foo\\{}", dirs::home_dir().unwrap().to_str().unwrap(), std::env::var("USERNAME").unwrap()))]
#[case("%USERPROFILE%\\foo\\%USERNAME%\\bar", format!("{}\\foo\\{}\\bar", dirs::home_dir().unwrap().to_str().unwrap(), std::env::var("USERNAME").unwrap()))]
fn test_expand_env_var(#[case] input: &str, #[case] expected: String) {
assert_eq!(expand_env_var(input).unwrap(), expected);
}
#[rstest]
#[case("%USERPROFILE")]
#[case("%USERPROFILE2%")]
#[case("%%")]
fn test_expand_env_var_error(#[case] input: &str) {
assert!(expand_env_var(input).is_err());
}
}