Expand tilde in Rust Path idiomatically

  1. The most idiomatic way would be to just use an existing crate, in this case shellexpand (github, crates.io) seems to do what you want:

    extern crate shellexpand; // 1.0.0
    
    #[test]
    fn test_shellexpand() {
        let home = std::env::var("HOME").unwrap();
        assert_eq!(shellexpand::tilde("~/foo"), format!("{}/foo", home));
    }
    
  2. Alternatively, you could try it with dirs (crates.io). Here is a sketch:

    extern crate dirs; // 1.0.4
    
    use std::path::{Path, PathBuf};
    
    fn expand_tilde<P: AsRef<Path>>(path_user_input: P) -> Option<PathBuf> {
        let p = path_user_input.as_ref();
        if !p.starts_with("~") {
            return Some(p.to_path_buf());
        }
        if p == Path::new("~") {
            return dirs::home_dir();
        }
        dirs::home_dir().map(|mut h| {
            if h == Path::new("https://stackoverflow.com/") {
                // Corner case: `h` root directory;
                // don't prepend extra `/`, just drop the tilde.
                p.strip_prefix("~").unwrap().to_path_buf()
            } else {
                h.push(p.strip_prefix("~/").unwrap());
                h
            }
        })
    }
    

    Usage examples:

    #[test]
    fn test_expand_tilde() {
        // Should work on your linux box during tests, would fail in stranger
        // environments!
        let home = std::env::var("HOME").unwrap();
        let projects = PathBuf::from(format!("{}/Projects", home));
        assert_eq!(expand_tilde("~/Projects"), Some(projects));
        assert_eq!(expand_tilde("/foo/bar"), Some("/foo/bar".into()));
        assert_eq!(
            expand_tilde("~alice/projects"),
            Some("~alice/projects".into())
        );
    }
    

    Some remarks:

    • The P: AsRef<Path> input type imitates what the standard
      library does. This is why the method accepts all Path-like
      inputs, like &str, &OsStr, and &Path.
    • Path::new doesn’t allocate anything, it points to
      exactly the same bytes as the &str.
    • strip_prefix("~/").unwrap() should never fail here,
      because we checked that the path starts with ~ and
      is not just ~. The only way how this can be is that
      the path starts with ~/ (because of how starts_with
      is defined).

Leave a Comment

Hata!: SQLSTATE[HY000] [1045] Access denied for user 'divattrend_liink'@'localhost' (using password: YES)