std::fs::canonicalize 对于不存在的文件
std::fs::canonicalize for files that don't exist
我正在用 Rust 编写一个程序,它在用户定义的路径中创建一个文件。我需要能够规范化中间组件(~/
应该变成 $HOME/
,../
应该进入一个目录,等等)以便在正确的位置创建文件。 std::fs::canonicalize 几乎完全符合我的要求,但如果路径不存在,它会出现恐慌。
是否有一个函数可以像 std::fs::canonicalize
一样规范化组件,但如果文件不存在则不会恐慌?
这样的函数不是标准的有充分的理由:
当您同时处理 link 和不存在的文件时,没有唯一的路径。如果 a/b
是 link 到 c/d/e
,那么 a/b/../f
可能意味着 a/f
或 c/d/f
~
快捷方式是一项 shell 功能。您可能想概括它(我这样做),但这是一个不明显的选择,尤其是当您考虑 ~
在大多数系统中是有效文件名时。
这就是说,它有时很有用,因为由于您的应用程序的性质,这些歧义不是问题。
在这种情况下 what I do:
use {
directories::UserDirs,
lazy_regex::*,
std::path::{Path, PathBuf},
};
/// build a usable path from a user input which may be absolute
/// (if it starts with / or ~) or relative to the supplied base_dir.
/// (we might want to try detect windows drives in the future, too)
pub fn path_from<P: AsRef<Path>>(
base_dir: P,
input: &str,
) -> PathBuf {
let tilde = regex!(r"^~(/|$)");
if input.starts_with('/') {
// if the input starts with a `/`, we use it as is
input.into()
} else if tilde.is_match(input) {
// if the input starts with `~` as first token, we replace
// this `~` with the user home directory
PathBuf::from(
&*tilde
.replace(input, |c: &Captures| {
if let Some(user_dirs) = UserDirs::new() {
format!(
"{}{}",
user_dirs.home_dir().to_string_lossy(),
&c[1],
)
} else {
warn!("no user dirs found, no expansion of ~");
c[0].to_string()
}
})
)
} else {
// we put the input behind the source (the selected directory
// or its parent) and we normalize so that the user can type
// paths with `../`
normalize_path(base_dir.join(input))
}
}
/// Improve the path to try remove and solve .. token.
///
/// This assumes that `a/b/../c` is `a/c` which might be different from
/// what the OS would have chosen when b is a link. This is OK
/// for broot verb arguments but can't be generally used elsewhere
///
/// This function ensures a given path ending with '/' still
/// ends with '/' after normalization.
pub fn normalize_path<P: AsRef<Path>>(path: P) -> PathBuf {
let ends_with_slash = path.as_ref()
.to_str()
.map_or(false, |s| s.ends_with('/'));
let mut normalized = PathBuf::new();
for component in path.as_ref().components() {
match &component {
Component::ParentDir => {
if !normalized.pop() {
normalized.push(component);
}
}
_ => {
normalized.push(component);
}
}
}
if ends_with_slash {
normalized.push("");
}
normalized
}
(这使用 directories crate 以跨平台方式获取主页,但存在其他板条箱,您也可以在大多数平台中读取 $HOME
env 变量)
我正在用 Rust 编写一个程序,它在用户定义的路径中创建一个文件。我需要能够规范化中间组件(~/
应该变成 $HOME/
,../
应该进入一个目录,等等)以便在正确的位置创建文件。 std::fs::canonicalize 几乎完全符合我的要求,但如果路径不存在,它会出现恐慌。
是否有一个函数可以像 std::fs::canonicalize
一样规范化组件,但如果文件不存在则不会恐慌?
这样的函数不是标准的有充分的理由:
当您同时处理 link 和不存在的文件时,没有唯一的路径。如果
a/b
是 link 到c/d/e
,那么a/b/../f
可能意味着a/f
或c/d/f
~
快捷方式是一项 shell 功能。您可能想概括它(我这样做),但这是一个不明显的选择,尤其是当您考虑~
在大多数系统中是有效文件名时。
这就是说,它有时很有用,因为由于您的应用程序的性质,这些歧义不是问题。
在这种情况下 what I do:
use {
directories::UserDirs,
lazy_regex::*,
std::path::{Path, PathBuf},
};
/// build a usable path from a user input which may be absolute
/// (if it starts with / or ~) or relative to the supplied base_dir.
/// (we might want to try detect windows drives in the future, too)
pub fn path_from<P: AsRef<Path>>(
base_dir: P,
input: &str,
) -> PathBuf {
let tilde = regex!(r"^~(/|$)");
if input.starts_with('/') {
// if the input starts with a `/`, we use it as is
input.into()
} else if tilde.is_match(input) {
// if the input starts with `~` as first token, we replace
// this `~` with the user home directory
PathBuf::from(
&*tilde
.replace(input, |c: &Captures| {
if let Some(user_dirs) = UserDirs::new() {
format!(
"{}{}",
user_dirs.home_dir().to_string_lossy(),
&c[1],
)
} else {
warn!("no user dirs found, no expansion of ~");
c[0].to_string()
}
})
)
} else {
// we put the input behind the source (the selected directory
// or its parent) and we normalize so that the user can type
// paths with `../`
normalize_path(base_dir.join(input))
}
}
/// Improve the path to try remove and solve .. token.
///
/// This assumes that `a/b/../c` is `a/c` which might be different from
/// what the OS would have chosen when b is a link. This is OK
/// for broot verb arguments but can't be generally used elsewhere
///
/// This function ensures a given path ending with '/' still
/// ends with '/' after normalization.
pub fn normalize_path<P: AsRef<Path>>(path: P) -> PathBuf {
let ends_with_slash = path.as_ref()
.to_str()
.map_or(false, |s| s.ends_with('/'));
let mut normalized = PathBuf::new();
for component in path.as_ref().components() {
match &component {
Component::ParentDir => {
if !normalized.pop() {
normalized.push(component);
}
}
_ => {
normalized.push(component);
}
}
}
if ends_with_slash {
normalized.push("");
}
normalized
}
(这使用 directories crate 以跨平台方式获取主页,但存在其他板条箱,您也可以在大多数平台中读取 $HOME
env 变量)