std::fs::canonicalize 不存在的文件

Aay*_*yla 6 path rust

我正在用 Rust 编写一个程序,在用户定义的路径中创建一个文件。我需要能够规范化中间组件(~/应该成为$HOME/,../应该进入目录等),以便在正确的位置创建文件。std::fs::canonicalize几乎完全符合我的要求,但如果路径尚不存在,它会出现恐慌。

是否有一个函数可以以相同的方式标准化组件,std::fs::canonicalize但如果文件不存在则不会出现恐慌?

Den*_*ret 4

这样的函数不是标准的有充分的理由:

  1. 当您处理链接和不存在的文件时,没有唯一的路径。如果a/b是 的链接c/d/e,则a/b/../f可以表示a/f或c/d/f

  2. 该~快捷方式是 shell 功能。您可能想要概括它(我确实如此),但这是一个不明显的选择,特别是当您认为~是大多数系统中的有效文件名时。

话虽这么说,如果由于应用程序的性质而这些歧义不是问题,那么它有时很有用。

在这种情况下我会这样做:

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
}
Run Code Online (Sandbox Code Playgroud)

(这使用目录板条箱以跨平台方式获取主页,但存在其他板条箱,您也可以$HOME在大多数平台中读取 env 变量)