按习惯用法扩展Rust Path中的波浪号

yrdbyhpb  于 2023-08-05  发布在  其他
关注(0)|答案(2)|浏览(93)

有时候,例如在阅读一些配置文件时,您读取用户输入的文件路径,而不需要通过shell(例如,您得到~/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()来保持示例的简短。在生产代码中应该有一些错误处理。
332nm8kg

332nm8kg1#

1.最惯用的方法是只使用现有的crate,在这种情况下,shellexpandgithubcrates.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));
}

字符串

  • 或者,您可以尝试使用dirscrates.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("~") {
        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
        }
    })
}


使用示例:

#[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是如何定义的)。
vm0i2vca

vm0i2vca2#

编辑

expanduser crate可能可以实现您想要的一切 ,包括~user 的扩展。示例(改编自文档):

use expanduser::expanduser;

let path = expanduser("~foolmeonce/path/to/directory")?;
assert_eq!(path.display().to_string(), "/home/foolmeonce/path/to/directory");

let path = expanduser("~/path/to/directory")?;
assert_eq!(path.display().to_string(), "/home/foolmetwice/path/to/directory");

字符串

原始答案

下面是一个返回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()
}


注意事项:

  • 主目录最多检索一次。
  • 唯一可能失败的unwraplazy_static!块中的那个,但无法从中恢复。
  • 唯一可能的分配发生在join中。

一些用法示例:

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

相关问题