Расширять тильду в Rust Path идиоматически - PullRequest
0 голосов
/ 19 января 2019

Иногда, например, при чтении какого-либо файла конфигурации, вы читаете путь к файлу, введенный пользователем, не проходя через оболочку (например, вы получаете ~/test).

Как Option 2 ниже:пишу в тестовый файл в домашнем каталоге пользователя, мне интересно, есть ли что-то более идиоматическое, чем Option 1.

use std::env::var;
use std::fs::File;
use std::io::prelude::*;
use std::path::Path;

fn write_to(path: &Path) {
    let mut f = File::create(path).unwrap();
    f.write_all("Hi".as_bytes()).unwrap();
}

fn main() {
    // Option 1
    let from_env = format!("{}/test", var("HOME").unwrap());
    let with_var = Path::new(&from_env);
    // Create $HOME/test
    write_to(with_var);

    // Option 2
    let with_tilde = Path::new("~/test");
    // Create the test file in current directory, provided a directory ./~ exists
    write_to(with_tilde);
}

Примечание : unwrap()используется здесь, чтобы сохранить пример коротким.Должна быть некоторая обработка ошибок в рабочем коде.

1 Ответ

0 голосов
/ 22 января 2019
  1. Наиболее идиоматичным способом было бы просто использовать существующий ящик, в этом случае shellexpand ( github , crates.io ), кажется, делает то, что вы хотите :

    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. Кроме того, вы можете попробовать это с dirs ( crates.io ). Вот эскиз:

    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("~") {
            if p == Path::new("~") {
                dirs::home_dir()
            } else {
                dirs::home_dir().map(|mut h| {
                    if h == Path::new("/") {
                        // 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
                    }
                })
            }
        } else {
            Some(p.to_path_buf())
        }
    }
    

    Примеры использования:

    #[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())
        );
    }
    

    Некоторые замечания:

    • Тип ввода P: AsRef<Path> имитирует стандарт библиотека делает. Вот почему метод принимает все Path -подобные входы, такие как &str, &OsStr и &Path.
    • Path::new ничего не выделяет, он указывает на точно такие же байты, что и &str.
    • strip_prefix("~/").unwrap() никогда не должен потерпеть неудачу здесь, потому что мы проверили, что путь начинается с ~ и это не просто ~. Единственный способ, которым это может быть путь начинается с ~/ (из-за того, как starts_with определяется).
...