惯用地在 Rust Path 中展开波浪号
Expand tilde in Rust Path idiomatically
有时,例如在读取某些配置文件时,您读取用户输入的文件路径而没有经过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()
是为了让例子简短。生产代码中应该有一些错误处理。
最惯用的方法是只使用现有的板条箱,在这种情况下 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));
}
或者,您可以尝试使用 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("~") {
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
-like
输入,例如 &str
、&OsStr
和 &Path
.
Path::new
没有分配任何东西,它指向
与 &str
. 完全相同的字节
strip_prefix("~/").unwrap()
应该永远不会在这里失败,
因为我们检查了路径以 ~
开头并且
不只是 ~
。唯一的办法就是
路径以 ~/
开头(因为 starts_with
已定义)。
这是一个返回 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()
}
注意事项:
- 主目录最多检索一次。
- 唯一可能失败的
unwrap
是 lazy_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")
);
}
有时,例如在读取某些配置文件时,您读取用户输入的文件路径而没有经过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()
是为了让例子简短。生产代码中应该有一些错误处理。
最惯用的方法是只使用现有的板条箱,在这种情况下
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)); }
或者,您可以尝试使用
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("~") { 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
-like 输入,例如&str
、&OsStr
和&Path
.Path::new
没有分配任何东西,它指向 与&str
. 完全相同的字节
strip_prefix("~/").unwrap()
应该永远不会在这里失败, 因为我们检查了路径以~
开头并且 不只是~
。唯一的办法就是 路径以~/
开头(因为starts_with
已定义)。
这是一个返回 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()
}
注意事项:
- 主目录最多检索一次。
- 唯一可能失败的
unwrap
是lazy_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")
);
}