mirror of
https://github.com/nushell/nushell
synced 2024-11-15 09:27:08 +00:00
Unified path expansion under new module and better canonicalize (#1571)
* New 'path' module under nu-cli. Added normalize and canonicalize method. Added some unit tests. * Replace old usages of normalize and canonicalize. * Fix reading symlinks and existence logic. * Better explained
This commit is contained in:
parent
c0dda36217
commit
18dd009ca8
3 changed files with 194 additions and 52 deletions
|
@ -17,6 +17,7 @@ mod evaluate;
|
|||
mod format;
|
||||
mod futures;
|
||||
mod git;
|
||||
mod path;
|
||||
mod shell;
|
||||
mod stream;
|
||||
mod utils;
|
||||
|
|
184
crates/nu-cli/src/path.rs
Normal file
184
crates/nu-cli/src/path.rs
Normal file
|
@ -0,0 +1,184 @@
|
|||
// Copyright (C) 2020 Kevin Dc
|
||||
//
|
||||
// This file is part of nushell.
|
||||
//
|
||||
// nushell is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// nushell is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with nushell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::io;
|
||||
use std::path::{Component, Path, PathBuf};
|
||||
|
||||
pub fn normalize(path: impl AsRef<Path>) -> PathBuf {
|
||||
let mut normalized = PathBuf::new();
|
||||
for component in path.as_ref().components() {
|
||||
match component {
|
||||
Component::Normal(normal) => {
|
||||
if let Some(normal) = normal.to_str() {
|
||||
if normal.chars().all(|c| c == '.') {
|
||||
for _ in 0..(normal.len() - 1) {
|
||||
normalized.push("..");
|
||||
}
|
||||
} else {
|
||||
normalized.push(normal);
|
||||
}
|
||||
} else {
|
||||
normalized.push(normal);
|
||||
}
|
||||
}
|
||||
c => normalized.push(c.as_os_str()),
|
||||
}
|
||||
}
|
||||
|
||||
normalized
|
||||
}
|
||||
|
||||
pub struct AllowMissing(pub bool);
|
||||
|
||||
pub fn canonicalize<P, Q>(
|
||||
relative_to: P,
|
||||
path: Q,
|
||||
allow_missing: AllowMissing,
|
||||
) -> io::Result<PathBuf>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
Q: AsRef<Path>,
|
||||
{
|
||||
let path = normalize(path);
|
||||
let (relative_to, path) = if path.is_absolute() {
|
||||
let components: Vec<_> = path.components().collect();
|
||||
let separator = components
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, c)| c == &&Component::CurDir || c == &&Component::ParentDir);
|
||||
|
||||
if let Some((index, _)) = separator {
|
||||
let (absolute, relative) = components.split_at(index);
|
||||
let absolute: PathBuf = absolute.iter().collect();
|
||||
let relative: PathBuf = relative.iter().collect();
|
||||
|
||||
(absolute, relative)
|
||||
} else {
|
||||
(relative_to.as_ref().to_path_buf(), path)
|
||||
}
|
||||
} else {
|
||||
(relative_to.as_ref().to_path_buf(), path)
|
||||
};
|
||||
|
||||
let path = if path.is_relative() {
|
||||
let mut result = relative_to;
|
||||
path.components().for_each(|component| match component {
|
||||
Component::ParentDir => {
|
||||
result.pop();
|
||||
}
|
||||
Component::Normal(normal) => result.push(normal),
|
||||
_ => {}
|
||||
});
|
||||
|
||||
result
|
||||
} else {
|
||||
path
|
||||
};
|
||||
|
||||
let path = match std::fs::read_link(&path) {
|
||||
Ok(resolved) => resolved,
|
||||
Err(e) => {
|
||||
// We are here if path doesn't exist or isn't a symlink
|
||||
if allow_missing.0 || path.exists() {
|
||||
// Return if we allow missing paths or if the path
|
||||
// actually exists, but wasn't a symlink
|
||||
path
|
||||
} else {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// De-UNC paths
|
||||
Ok(dunce::simplified(&path).to_path_buf())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::io;
|
||||
|
||||
#[test]
|
||||
fn normalize_three_dots() {
|
||||
assert_eq!(PathBuf::from("../.."), normalize("..."));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normalize_three_dots_with_redundant_dot() {
|
||||
assert_eq!(PathBuf::from("./../.."), normalize("./..."));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn canonicalize_two_dots_and_allow_missing() -> io::Result<()> {
|
||||
let relative_to = Path::new("/foo/bar"); // does not exists
|
||||
let path = Path::new("..");
|
||||
|
||||
assert_eq!(
|
||||
PathBuf::from("/foo"),
|
||||
canonicalize(relative_to, path, AllowMissing(true))?
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn canonicalize_three_dots_and_allow_missing() -> io::Result<()> {
|
||||
let relative_to = Path::new("/foo/bar/baz"); // missing path
|
||||
let path = Path::new("...");
|
||||
|
||||
assert_eq!(
|
||||
PathBuf::from("/foo"),
|
||||
canonicalize(relative_to, path, AllowMissing(true))?
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn canonicalize_three_dots_with_redundant_dot_and_allow_missing() -> io::Result<()> {
|
||||
let relative_to = Path::new("/foo/bar/baz"); // missing path
|
||||
let path = Path::new("./...");
|
||||
|
||||
assert_eq!(
|
||||
PathBuf::from("/foo"),
|
||||
canonicalize(relative_to, path, AllowMissing(true))?
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn canonicalize_three_dots_and_disallow_missing() -> io::Result<()> {
|
||||
let relative_to = Path::new("/foo/bar/"); // root is not missing
|
||||
let path = Path::new("...");
|
||||
|
||||
assert_eq!(
|
||||
PathBuf::from("/"),
|
||||
canonicalize(relative_to, path, AllowMissing(false))?
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn canonicalize_three_dots_and_disallow_missing_should_fail() {
|
||||
let relative_to = Path::new("/foo/bar/baz"); // foo is missing
|
||||
let path = Path::new("...");
|
||||
|
||||
assert!(canonicalize(relative_to, path, AllowMissing(false)).is_err());
|
||||
}
|
||||
}
|
|
@ -5,6 +5,7 @@ use crate::commands::mkdir::MkdirArgs;
|
|||
use crate::commands::mv::MoveArgs;
|
||||
use crate::commands::rm::RemoveArgs;
|
||||
use crate::data::dir_entry_dict;
|
||||
use crate::path::{canonicalize, normalize, AllowMissing};
|
||||
use crate::prelude::*;
|
||||
use crate::shell::completer::NuCompleter;
|
||||
use crate::shell::shell::Shell;
|
||||
|
@ -187,13 +188,14 @@ impl Shell for FilesystemShell {
|
|||
if target == Path::new("-") {
|
||||
PathBuf::from(&self.last_path)
|
||||
} else {
|
||||
let path = canonicalize(self.path(), target).map_err(|_| {
|
||||
ShellError::labeled_error(
|
||||
"Cannot change to directory",
|
||||
"directory not found",
|
||||
&v.tag,
|
||||
)
|
||||
})?;
|
||||
let path =
|
||||
canonicalize(self.path(), target, AllowMissing(false)).map_err(|_| {
|
||||
ShellError::labeled_error(
|
||||
"Cannot change to directory",
|
||||
"directory not found",
|
||||
&v.tag,
|
||||
)
|
||||
})?;
|
||||
|
||||
if !path.is_dir() {
|
||||
return Err(ShellError::labeled_error(
|
||||
|
@ -1164,48 +1166,3 @@ fn is_hidden_dir(dir: impl AsRef<Path>) -> bool {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn normalize(path: impl AsRef<Path>) -> PathBuf {
|
||||
let mut normalized = PathBuf::new();
|
||||
for component in path.as_ref().components() {
|
||||
match component {
|
||||
Component::Normal(normal) => {
|
||||
if let Some(normal) = normal.to_str() {
|
||||
if normal.chars().all(|c| c == '.') {
|
||||
for _ in 0..(normal.len() - 1) {
|
||||
normalized.push("..");
|
||||
}
|
||||
} else {
|
||||
normalized.push(normal);
|
||||
}
|
||||
} else {
|
||||
normalized.push(normal);
|
||||
}
|
||||
}
|
||||
c => normalized.push(c.as_os_str()),
|
||||
}
|
||||
}
|
||||
|
||||
normalized
|
||||
}
|
||||
|
||||
fn canonicalize(relative_to: impl AsRef<Path>, path: impl AsRef<Path>) -> std::io::Result<PathBuf> {
|
||||
let path = if path.as_ref().is_relative() {
|
||||
let mut result = relative_to.as_ref().to_path_buf();
|
||||
normalize(path.as_ref())
|
||||
.components()
|
||||
.for_each(|component| match component {
|
||||
Component::ParentDir => {
|
||||
result.pop();
|
||||
}
|
||||
Component::Normal(normal) => result.push(normal),
|
||||
_ => {}
|
||||
});
|
||||
|
||||
result
|
||||
} else {
|
||||
path.as_ref().into()
|
||||
};
|
||||
|
||||
dunce::canonicalize(path)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue