惯用地扩展 Rust Path 中的波浪号 [英] Expand tilde in Rust Path idiomatically
问题描述
有时,例如在读取某个配置文件时,您会读取用户输入的文件路径,而无需通过 shell(例如,您得到 ~/test
).
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
).
由于下面的 Option 2
没有写入用户主目录中的测试文件,我想知道是否有比 Option 1
更惯用的东西.
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
.
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()
以保持示例简短.生产代码中应该有一些错误处理.
Note: unwrap()
is used here to keep the example short. There should be some error handling in production code.
推荐答案
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));
}
或者,您可以尝试使用 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
-like输入,例如&str
、&OsStr
和&Path
.Path::new
不分配任何东西,它指向与&str
完全相同的字节.strip_prefix("~/").unwrap()
不应该在这里失败,因为我们检查了路径是否以~
开头并且不仅仅是~
.唯一的办法就是路径以~/
开头(因为starts_with
已定义).
- The
P: AsRef<Path>
input type imitates what the standard library does. This is why the method accepts allPath
-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 howstarts_with
is defined).
这篇关于惯用地扩展 Rust Path 中的波浪号的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!