From 7a202aa8b931befac28273836535ed6580e92b82 Mon Sep 17 00:00:00 2001 From: Takayuki Maeda <41065217+TaKO8Ki@users.noreply.github.com> Date: Mon, 20 Sep 2021 11:49:29 +0900 Subject: [PATCH] Expand tilde and environment variables in path field (#120) * expand tlide in path field * join path * implement `expand_path` * fix compile error on Windows * fix typo * use HOMEPATH * fix home dir * use HOME env --- sample.toml | 2 +- src/config.rs | 95 +++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 94 insertions(+), 3 deletions(-) diff --git a/sample.toml b/sample.toml index f76968f..c6cf3b1 100644 --- a/sample.toml +++ b/sample.toml @@ -26,4 +26,4 @@ database = "dvdrental" [[conn]] type = "sqlite" -path = "/Users/tako8ki/Downloads/chinook.db" +path = "$HOME/Downloads/chinook.db" diff --git a/src/config.rs b/src/config.rs index 5b0c07b..e716db4 100644 --- a/src/config.rs +++ b/src/config.rs @@ -4,6 +4,7 @@ use serde::Deserialize; use std::fmt; use std::fs::File; use std::io::{BufReader, Read}; +use std::path::{Path, PathBuf}; use structopt::StructOpt; #[derive(StructOpt, Debug)] @@ -244,10 +245,12 @@ impl Connection { DatabaseType::Sqlite => { let path = self.path.as_ref().map_or( Err(anyhow::anyhow!("type sqlite needs the path field")), - |path| Ok(path.to_str().unwrap()), + |path| { + expand_path(path).ok_or_else(|| anyhow::anyhow!("cannot expand file path")) + }, )?; - Ok(format!("sqlite://{path}", path = path)) + Ok(format!("sqlite://{path}", path = path.to_str().unwrap())) } } } @@ -273,3 +276,91 @@ pub fn get_app_config_path() -> anyhow::Result { std::fs::create_dir_all(&path)?; Ok(path) } + +fn expand_path(path: &Path) -> Option { + let mut expanded_path = PathBuf::new(); + let mut path_iter = path.iter(); + if path.starts_with("~") { + path_iter.next()?; + expanded_path = expanded_path.join(dirs_next::home_dir()?); + } + for path in path_iter { + let path = path.to_str()?; + expanded_path = if cfg!(unix) && path.starts_with('$') { + expanded_path.join(std::env::var(path.strip_prefix('$')?).unwrap_or_default()) + } else if cfg!(windows) && path.starts_with('%') && path.ends_with('%') { + expanded_path + .join(std::env::var(path.strip_prefix('%')?.strip_suffix('%')?).unwrap_or_default()) + } else { + expanded_path.join(path) + } + } + Some(expanded_path) +} + +#[cfg(test)] +mod test { + use super::{expand_path, Path, PathBuf}; + use std::env; + + #[test] + #[cfg(unix)] + fn test_expand_path() { + let home = env::var("HOME").unwrap(); + let test_env = "baz"; + env::set_var("TEST", test_env); + + assert_eq!( + expand_path(&Path::new("$HOME/foo")), + Some(PathBuf::from(&home).join("foo")) + ); + + assert_eq!( + expand_path(&Path::new("$HOME/foo/$TEST/bar")), + Some(PathBuf::from(&home).join("foo").join(test_env).join("bar")) + ); + + assert_eq!( + expand_path(&Path::new("~/foo")), + Some(PathBuf::from(&home).join("foo")) + ); + + assert_eq!( + expand_path(&Path::new("~/foo/~/bar")), + Some(PathBuf::from(&home).join("foo").join("~").join("bar")) + ); + } + + #[test] + #[cfg(windows)] + fn test_expand_patha() { + let home = std::env::var("HOMEPATH").unwrap(); + let test_env = "baz"; + env::set_var("TEST", test_env); + + assert_eq!( + expand_path(&Path::new("%HOMEPATH%/foo")), + Some(PathBuf::from(&home).join("foo")) + ); + + assert_eq!( + expand_path(&Path::new("%HOMEPATH%/foo/%TEST%/bar")), + Some(PathBuf::from(&home).join("foo").join(test_env).join("bar")) + ); + + assert_eq!( + expand_path(&Path::new("~/foo")), + Some(PathBuf::from(&dirs_next::home_dir().unwrap()).join("foo")) + ); + + assert_eq!( + expand_path(&Path::new("~/foo/~/bar")), + Some( + PathBuf::from(&dirs_next::home_dir().unwrap()) + .join("foo") + .join("~") + .join("bar") + ) + ); + } +}