简体   繁体   English

习惯性地在 Rust Path 中展开波浪号

[英]Expand tilde in Rust Path idiomatically

Sometimes, for instance when reading some configuration file, you read a file path entered by the user without going through the shell (for instance, you get ~/test ).有时,例如在读取某个配置文件时,您无需通过 shell 就读取用户输入的文件路径(例如,您得到~/test )。

As Option 2 below doesn't write to test file in user home directory, I'm wondering if there is something more idiomatic than Option 1 .由于下面的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);
}

Note : unwrap() is used here to keep the example short.注意:这里使用unwrap()来保持示例简短。 There should be some error handling in production code.生产代码中应该有一些错误处理。

  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:最惯用的方法是只使用现有的 crate,在这种情况下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. Alternatively, you could try it with dirs ( crates.io ).或者,您可以使用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("/") { // 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. P: AsRef<Path>输入类型模仿标准库所做的。 This is why the method accepts all Path -like inputs, like &str , &OsStr , and &Path .这就是为什么该方法接受所有类似Path的输入,如&str&OsStr&Path
    • Path::new doesn't allocate anything, it points to exactly the same bytes as the &str . Path::new不分配任何东西,它指向与&str完全相同的字节。
    • strip_prefix("~/").unwrap() should never fail here, because we checked that the path starts with ~ and is not just ~ . strip_prefix("~/").unwrap()在这里永远不会失败,因为我们检查了路径以~开头,而不仅仅是~ The only way how this can be is that the path starts with ~/ (because of how starts_with is defined).唯一的方法是路径以~/开头(因为starts_with是如何定义的)。

Here's an implementation returning a Cow<Path> , so that we only allocate if there's actually a tilde prefix in the path:这是一个返回Cow<Path>的实现,因此我们仅在路径中实际上存在波浪号前缀时才分配:

use std::{borrow::Cow, path::Path};

use directories::UserDirs;
use lazy_static::lazy_static;

fn expand_home_dir<'a, P: AsRef<Path> + ?Sized>(path: &'a P) -> Cow<'a, Path> {
    let path = path.as_ref();

    if !path.starts_with("~") {
        return path.into();
    }

    lazy_static! {
        static ref HOME_DIR: &'static Path = UserDirs::new().unwrap().home_dir();
    }

    HOME_DIR.join(path.strip_prefix("~").unwrap()).into()
}

Things to notice:注意事项:

  • The home directory is retrieved at most once.主目录最多检索一次。
  • The only unwrap that could fail is the one in the lazy_static!唯一可能失败的unwraplazy_static! block, but there's no recovery from it.阻止,但无法从中恢复。
  • The only possible allocation happening is in join .唯一可能发生的分配是在join中。

Some usage examples:一些使用示例:

#[test]
fn test_expand_home_dir() {
    lazy_static! {
        static ref HOME_DIR: String = std::env::var("HOME").unwrap();
    }

    // Simple prefix expansion.
    assert_eq!(
        expand_home_dir("~/a/path/to/a/file"),
        Path::new(&format!("{}/a/path/to/a/file", &*HOME_DIR))
    );

    // Lone tilde is user's home directory.
    assert_eq!(expand_home_dir("~"), Path::new(&*HOME_DIR));

    // Tilde in the middle of a path should not be expanded.
    assert_eq!(
        expand_home_dir("/a/~/path/to/a/file"),
        Path::new("/a/~/path/to/a/file")
    );

    // No tilde, no expansion in absolute paths.
    assert_eq!(
        expand_home_dir("/a/path/to/a/file"),
        Path::new("/a/path/to/a/file")
    );

    // No tilde, no expansion in relative paths.
    assert_eq!(
        expand_home_dir("another/path/to/a/file"),
        Path::new("another/path/to/a/file")
    );
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM