use std::{
env,
path::{Path, PathBuf},
};
pub fn canonicalize_from(from: &Path, path: &Path) -> PathBuf {
path.components().fold(from.to_path_buf(), |mut acc, comp| {
match comp {
std::path::Component::ParentDir => {
acc.pop();
},
std::path::Component::CurDir => {},
other => acc.push(other.as_os_str()),
}
acc
})
}
pub fn expand_tilde(raw: &str) -> Option<PathBuf> {
if raw == "~" {
return dirs::home_dir();
}
if let Some(rest) = raw.strip_prefix("~/") {
let mut home = dirs::home_dir()?;
home.push(rest);
return Some(home);
}
Some(PathBuf::from(raw))
}
pub fn resolve_from<P: AsRef<Path>>(from: &Path, to_resolve: P) -> Option<PathBuf> {
let expanded = expand_tilde(to_resolve.as_ref().to_str()?)?;
if expanded.is_absolute() {
return Some(expanded);
}
Some(canonicalize_from(from, &expanded))
}
const VIK_HOME_ENV_NAME: &str = "VIK_HOME";
pub fn default_home() -> PathBuf {
env::var_os(VIK_HOME_ENV_NAME)
.map(PathBuf::from)
.unwrap_or_else(|| dirs::home_dir().unwrap_or_else(|| PathBuf::from("~/.vik")))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_canonicalize() {
let cwd = Path::new("/home/user");
assert_eq!(
canonicalize_from(cwd, Path::new("file.txt")),
PathBuf::from("/home/user/file.txt")
);
assert_eq!(
canonicalize_from(cwd, Path::new("/etc/config.yaml")),
PathBuf::from("/etc/config.yaml")
);
assert_eq!(
canonicalize_from(cwd, Path::new("../other/file.txt")),
PathBuf::from("/home/other/file.txt")
);
assert_eq!(
canonicalize_from(cwd, Path::new("./file.txt")),
PathBuf::from("/home/user/file.txt")
);
assert_eq!(
canonicalize_from(cwd, Path::new("dir/../file.txt")),
PathBuf::from("/home/user/file.txt")
);
}
#[test]
fn tilde_alone_expands_to_home() {
let expected = dirs::home_dir().expect("home dir available in test env");
assert_eq!(expand_tilde("~"), Some(expected));
}
#[test]
fn tilde_slash_expands_to_home_subpath() {
let expected = dirs::home_dir().expect("home dir available in test env").join("code/vik");
assert_eq!(expand_tilde("~/code/vik"), Some(expected));
}
#[test]
fn tilde_inside_the_path_is_literal() {
let out = expand_tilde("/opt/~/literal").expect("literal tilde returns a path");
assert_eq!(out, PathBuf::from("/opt/~/literal"));
}
#[test]
fn no_var_expansion() {
let out = expand_tilde("$HOME/code").expect("value returned unchanged");
assert_eq!(out, PathBuf::from("$HOME/code"));
}
#[test]
fn relative_joins_onto_workflow_dir() {
let dir = PathBuf::from("/tmp/workflows");
let out = resolve_from(&dir, "./prompts/issues.md").expect("relative path resolves");
assert_eq!(out, PathBuf::from("/tmp/workflows/./prompts/issues.md"));
}
#[test]
fn absolute_is_untouched() {
let dir = PathBuf::from("/tmp/workflows");
let out = resolve_from(&dir, "/etc/vik.yml").expect("absolute path resolves");
assert_eq!(out, PathBuf::from("/etc/vik.yml"));
}
}