mirror of
https://github.com/nushell/nushell.git
synced 2024-11-30 04:14:17 +01:00
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:
parent
57a009b8e6
commit
82d69305b6
@ -1,13 +1,18 @@
|
|||||||
use super::{operate, PathSubcommandArguments};
|
use super::{operate, PathSubcommandArguments};
|
||||||
use crate::prelude::*;
|
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_engine::WholeStreamCommand;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{ColumnPath, Signature, SyntaxShape, UntaggedValue, Value};
|
use nu_protocol::{ColumnPath, Signature, SyntaxShape, UntaggedValue, Value};
|
||||||
use std::path::{Path, PathBuf};
|
use nu_source::Span;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
pub struct PathExpand;
|
pub struct PathExpand;
|
||||||
|
|
||||||
struct PathExpandArguments {
|
struct PathExpandArguments {
|
||||||
|
strict: bool,
|
||||||
rest: Vec<ColumnPath>,
|
rest: Vec<ColumnPath>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -24,17 +29,23 @@ impl WholeStreamCommand for PathExpand {
|
|||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("path expand")
|
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")
|
.rest(SyntaxShape::ColumnPath, "Optionally operate by column path")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
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> {
|
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
|
||||||
let tag = args.call_info.name_tag.clone();
|
let tag = args.call_info.name_tag.clone();
|
||||||
let args = args.evaluate_once()?;
|
let args = args.evaluate_once()?;
|
||||||
let cmd_args = Arc::new(PathExpandArguments {
|
let cmd_args = Arc::new(PathExpandArguments {
|
||||||
|
strict: args.has_flag("strict"),
|
||||||
rest: args.rest(0)?,
|
rest: args.rest(0)?,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -43,32 +54,65 @@ impl WholeStreamCommand for PathExpand {
|
|||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![Example {
|
vec![
|
||||||
description: "Expand relative directories",
|
Example {
|
||||||
example: "echo 'C:\\Users\\joe\\foo\\..\\bar' | path expand",
|
description: "Expand an absolute path",
|
||||||
result: None,
|
example: r"'C:\Users\joe\foo\..\bar' | path expand",
|
||||||
// fails to canonicalize into Some(vec![Value::from("C:\\Users\\joe\\bar")]) due to non-existing path
|
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))]
|
#[cfg(not(windows))]
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
vec![Example {
|
vec![
|
||||||
description: "Expand relative directories",
|
Example {
|
||||||
example: "echo '/home/joe/foo/../bar' | path expand",
|
description: "Expand an absolute path",
|
||||||
result: None,
|
example: "'/home/joe/foo/../bar' | path expand",
|
||||||
// fails to canonicalize into Some(vec![Value::from("/home/joe/bar")]) due to non-existing path
|
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 {
|
fn action(path: &Path, tag: Tag, args: &PathExpandArguments) -> Value {
|
||||||
let ps = path.to_string_lossy();
|
if let Ok(p) = dunce::canonicalize(path) {
|
||||||
let expanded = shellexpand::tilde(&ps);
|
UntaggedValue::filepath(p).into_value(tag)
|
||||||
let path: &Path = expanded.as_ref().as_ref();
|
} 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)))
|
UntaggedValue::filepath(resolve_dots(&path)).into_value(tag)
|
||||||
.into_value(tag)
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -1,6 +1,25 @@
|
|||||||
use std::io;
|
use std::io;
|
||||||
use std::path::{Component, Path, PathBuf};
|
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
|
pub fn absolutize<P, Q>(relative_to: P, path: Q) -> PathBuf
|
||||||
where
|
where
|
||||||
P: AsRef<Path>,
|
P: AsRef<Path>,
|
||||||
@ -67,7 +86,7 @@ where
|
|||||||
|
|
||||||
// borrowed from here https://stackoverflow.com/questions/54267608/expand-tilde-in-rust-path-idiomatically
|
// borrowed from here https://stackoverflow.com/questions/54267608/expand-tilde-in-rust-path-idiomatically
|
||||||
#[cfg(feature = "dirs")]
|
#[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();
|
let p = path_user_input.as_ref();
|
||||||
if !p.starts_with("~") {
|
if !p.starts_with("~") {
|
||||||
return Some(p.to_path_buf());
|
return Some(p.to_path_buf());
|
||||||
|
Loading…
Reference in New Issue
Block a user