Path expand fixes (#3505)

* Throw an error if path failed to expand

Previously, it just repeated the non-expanded path.

* Allow expanding non-existent paths

This commit has a strange error in examples.

* Specify span manually in examples; Add an example

* Expand relative path without requiring cwd

* Remove redundant tilde expansion

This makes the tilde expansion in relative paths dependant on "dirs"
feature.

* Add missing example result

* Adjust path expand description

* Fix import error with missing feature
This commit is contained in:
Jakub Žádník 2021-06-06 20:28:55 +03:00 committed by GitHub
parent 57a009b8e6
commit 82d69305b6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 84 additions and 21 deletions

View File

@ -1,13 +1,18 @@
use super::{operate, PathSubcommandArguments};
use crate::prelude::*;
#[cfg(feature = "dirs")]
use nu_engine::filesystem::path::expand_tilde;
use nu_engine::filesystem::path::resolve_dots;
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::{ColumnPath, Signature, SyntaxShape, UntaggedValue, Value};
use std::path::{Path, PathBuf};
use nu_source::Span;
use std::path::Path;
pub struct PathExpand;
struct PathExpandArguments {
strict: bool,
rest: Vec<ColumnPath>,
}
@ -24,17 +29,23 @@ impl WholeStreamCommand for PathExpand {
fn signature(&self) -> Signature {
Signature::build("path expand")
.switch(
"strict",
"Throw an error if the path could not be expanded",
Some('s'),
)
.rest(SyntaxShape::ColumnPath, "Optionally operate by column path")
}
fn usage(&self) -> &str {
"Expand a path to its absolute form"
"Try to expand a path to its absolute form"
}
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone();
let args = args.evaluate_once()?;
let cmd_args = Arc::new(PathExpandArguments {
strict: args.has_flag("strict"),
rest: args.rest(0)?,
});
@ -43,32 +54,65 @@ impl WholeStreamCommand for PathExpand {
#[cfg(windows)]
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Expand relative directories",
example: "echo 'C:\\Users\\joe\\foo\\..\\bar' | path expand",
result: None,
// fails to canonicalize into Some(vec![Value::from("C:\\Users\\joe\\bar")]) due to non-existing path
}]
vec![
Example {
description: "Expand an absolute path",
example: r"'C:\Users\joe\foo\..\bar' | path expand",
result: Some(vec![
UntaggedValue::filepath(r"C:\Users\joe\bar").into_value(Span::new(0, 25))
]),
},
Example {
description: "Expand a relative path",
example: r"'foo\..\bar' | path expand",
result: Some(vec![
UntaggedValue::filepath("bar").into_value(Span::new(0, 12))
]),
},
]
}
#[cfg(not(windows))]
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Expand relative directories",
example: "echo '/home/joe/foo/../bar' | path expand",
result: None,
// fails to canonicalize into Some(vec![Value::from("/home/joe/bar")]) due to non-existing path
}]
vec![
Example {
description: "Expand an absolute path",
example: "'/home/joe/foo/../bar' | path expand",
result: Some(vec![
UntaggedValue::filepath("/home/joe/bar").into_value(Span::new(0, 22))
]),
},
Example {
description: "Expand a relative path",
example: "'foo/../bar' | path expand",
result: Some(vec![
UntaggedValue::filepath("bar").into_value(Span::new(0, 12))
]),
},
]
}
}
fn action(path: &Path, tag: Tag, _args: &PathExpandArguments) -> Value {
let ps = path.to_string_lossy();
let expanded = shellexpand::tilde(&ps);
let path: &Path = expanded.as_ref().as_ref();
fn action(path: &Path, tag: Tag, args: &PathExpandArguments) -> Value {
if let Ok(p) = dunce::canonicalize(path) {
UntaggedValue::filepath(p).into_value(tag)
} else if args.strict {
Value::error(ShellError::labeled_error(
"Could not expand path",
"could not be expanded (path might not exist, non-final \
component is not a directory, or other cause)",
tag.span,
))
} else {
// "best effort" mode, just expand tilde and resolve single/double dots
#[cfg(feature = "dirs")]
let path = match expand_tilde(path) {
Some(expanded) => expanded,
None => path.into(),
};
UntaggedValue::filepath(dunce::canonicalize(path).unwrap_or_else(|_| PathBuf::from(path)))
.into_value(tag)
UntaggedValue::filepath(resolve_dots(&path)).into_value(tag)
}
}
#[cfg(test)]

View File

@ -1,6 +1,25 @@
use std::io;
use std::path::{Component, Path, PathBuf};
pub fn resolve_dots<P>(path: P) -> PathBuf
where
P: AsRef<Path>,
{
let mut result = PathBuf::new();
path.as_ref()
.components()
.for_each(|component| match component {
Component::ParentDir => {
result.pop();
}
Component::CurDir => {}
_ => result.push(component),
});
dunce::simplified(&result).to_path_buf()
}
pub fn absolutize<P, Q>(relative_to: P, path: Q) -> PathBuf
where
P: AsRef<Path>,
@ -67,7 +86,7 @@ where
// borrowed from here https://stackoverflow.com/questions/54267608/expand-tilde-in-rust-path-idiomatically
#[cfg(feature = "dirs")]
fn expand_tilde<P: AsRef<Path>>(path_user_input: P) -> Option<PathBuf> {
pub 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());