mirror of
https://github.com/nushell/nushell.git
synced 2024-11-22 08:23:24 +01:00
parent
b9f1371994
commit
a8f6a13239
25
Cargo.lock
generated
25
Cargo.lock
generated
@ -3254,6 +3254,7 @@ dependencies = [
|
|||||||
"nu-engine",
|
"nu-engine",
|
||||||
"nu-errors",
|
"nu-errors",
|
||||||
"nu-parser",
|
"nu-parser",
|
||||||
|
"nu-path",
|
||||||
"nu-plugin",
|
"nu-plugin",
|
||||||
"nu-protocol",
|
"nu-protocol",
|
||||||
"nu-source",
|
"nu-source",
|
||||||
@ -3373,7 +3374,6 @@ dependencies = [
|
|||||||
"serde_yaml",
|
"serde_yaml",
|
||||||
"sha2 0.9.5",
|
"sha2 0.9.5",
|
||||||
"shadow-rs",
|
"shadow-rs",
|
||||||
"shellexpand",
|
|
||||||
"strip-ansi-escapes",
|
"strip-ansi-escapes",
|
||||||
"sxd-document",
|
"sxd-document",
|
||||||
"sxd-xpath",
|
"sxd-xpath",
|
||||||
@ -3438,6 +3438,7 @@ dependencies = [
|
|||||||
"nu-errors",
|
"nu-errors",
|
||||||
"nu-json",
|
"nu-json",
|
||||||
"nu-parser",
|
"nu-parser",
|
||||||
|
"nu-path",
|
||||||
"nu-plugin",
|
"nu-plugin",
|
||||||
"nu-pretty-hex",
|
"nu-pretty-hex",
|
||||||
"nu-protocol",
|
"nu-protocol",
|
||||||
@ -3472,7 +3473,6 @@ dependencies = [
|
|||||||
"serde_yaml",
|
"serde_yaml",
|
||||||
"sha2 0.9.5",
|
"sha2 0.9.5",
|
||||||
"shadow-rs",
|
"shadow-rs",
|
||||||
"shellexpand",
|
|
||||||
"strip-ansi-escapes",
|
"strip-ansi-escapes",
|
||||||
"sxd-document",
|
"sxd-document",
|
||||||
"sxd-xpath",
|
"sxd-xpath",
|
||||||
@ -3501,6 +3501,7 @@ dependencies = [
|
|||||||
"nu-data",
|
"nu-data",
|
||||||
"nu-errors",
|
"nu-errors",
|
||||||
"nu-parser",
|
"nu-parser",
|
||||||
|
"nu-path",
|
||||||
"nu-protocol",
|
"nu-protocol",
|
||||||
"nu-source",
|
"nu-source",
|
||||||
"nu-test-support",
|
"nu-test-support",
|
||||||
@ -3571,6 +3572,7 @@ dependencies = [
|
|||||||
"nu-data",
|
"nu-data",
|
||||||
"nu-errors",
|
"nu-errors",
|
||||||
"nu-parser",
|
"nu-parser",
|
||||||
|
"nu-path",
|
||||||
"nu-plugin",
|
"nu-plugin",
|
||||||
"nu-protocol",
|
"nu-protocol",
|
||||||
"nu-source",
|
"nu-source",
|
||||||
@ -3639,16 +3641,24 @@ dependencies = [
|
|||||||
"itertools",
|
"itertools",
|
||||||
"log 0.4.14",
|
"log 0.4.14",
|
||||||
"nu-errors",
|
"nu-errors",
|
||||||
|
"nu-path",
|
||||||
"nu-protocol",
|
"nu-protocol",
|
||||||
"nu-source",
|
"nu-source",
|
||||||
"nu-test-support",
|
"nu-test-support",
|
||||||
"num-bigint 0.3.2",
|
"num-bigint 0.3.2",
|
||||||
"num-traits 0.2.14",
|
"num-traits 0.2.14",
|
||||||
"serde 1.0.126",
|
"serde 1.0.126",
|
||||||
"shellexpand",
|
|
||||||
"smart-default",
|
"smart-default",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nu-path"
|
||||||
|
version = "0.32.1"
|
||||||
|
dependencies = [
|
||||||
|
"dirs-next",
|
||||||
|
"dunce",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nu-plugin"
|
name = "nu-plugin"
|
||||||
version = "0.32.1"
|
version = "0.32.1"
|
||||||
@ -5812,15 +5822,6 @@ version = "1.0.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b6fa3938c99da4914afedd13bf3d79bcb6c277d1b2c398d23257a304d9e1b074"
|
checksum = "b6fa3938c99da4914afedd13bf3d79bcb6c277d1b2c398d23257a304d9e1b074"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "shellexpand"
|
|
||||||
version = "2.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "83bdb7831b2d85ddf4a7b148aa19d0587eddbe8671a436b7bd1182eaad0f2829"
|
|
||||||
dependencies = [
|
|
||||||
"dirs-next",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "signal-hook"
|
name = "signal-hook"
|
||||||
version = "0.1.17"
|
version = "0.1.17"
|
||||||
|
@ -25,6 +25,7 @@ nu-data = { version = "0.32.1", path = "./crates/nu-data" }
|
|||||||
nu-engine = { version = "0.32.1", path = "./crates/nu-engine" }
|
nu-engine = { version = "0.32.1", path = "./crates/nu-engine" }
|
||||||
nu-errors = { version = "0.32.1", path = "./crates/nu-errors" }
|
nu-errors = { version = "0.32.1", path = "./crates/nu-errors" }
|
||||||
nu-parser = { version = "0.32.1", path = "./crates/nu-parser" }
|
nu-parser = { version = "0.32.1", path = "./crates/nu-parser" }
|
||||||
|
nu-path = { version = "0.32.1", path = "./crates/nu-path" }
|
||||||
nu-plugin = { version = "0.32.1", path = "./crates/nu-plugin" }
|
nu-plugin = { version = "0.32.1", path = "./crates/nu-plugin" }
|
||||||
nu-protocol = { version = "0.32.1", path = "./crates/nu-protocol" }
|
nu-protocol = { version = "0.32.1", path = "./crates/nu-protocol" }
|
||||||
nu-source = { version = "0.32.1", path = "./crates/nu-source" }
|
nu-source = { version = "0.32.1", path = "./crates/nu-source" }
|
||||||
|
@ -83,7 +83,6 @@ serde_json = "1.0.61"
|
|||||||
serde_urlencoded = "0.7.0"
|
serde_urlencoded = "0.7.0"
|
||||||
serde_yaml = "0.8.16"
|
serde_yaml = "0.8.16"
|
||||||
sha2 = "0.9.3"
|
sha2 = "0.9.3"
|
||||||
shellexpand = "2.1.0"
|
|
||||||
strip-ansi-escapes = "0.1.0"
|
strip-ansi-escapes = "0.1.0"
|
||||||
sxd-document = "0.3.2"
|
sxd-document = "0.3.2"
|
||||||
sxd-xpath = "0.4.2"
|
sxd-xpath = "0.4.2"
|
||||||
|
@ -15,6 +15,7 @@ nu-data = { version = "0.32.1", path = "../nu-data" }
|
|||||||
nu-engine = { version = "0.32.1", path = "../nu-engine" }
|
nu-engine = { version = "0.32.1", path = "../nu-engine" }
|
||||||
nu-errors = { version = "0.32.1", path = "../nu-errors" }
|
nu-errors = { version = "0.32.1", path = "../nu-errors" }
|
||||||
nu-json = { version = "0.32.1", path = "../nu-json" }
|
nu-json = { version = "0.32.1", path = "../nu-json" }
|
||||||
|
nu-path = { version = "0.32.1", path = "../nu-path" }
|
||||||
nu-parser = { version = "0.32.1", path = "../nu-parser" }
|
nu-parser = { version = "0.32.1", path = "../nu-parser" }
|
||||||
nu-plugin = { version = "0.32.1", path = "../nu-plugin" }
|
nu-plugin = { version = "0.32.1", path = "../nu-plugin" }
|
||||||
nu-protocol = { version = "0.32.1", path = "../nu-protocol" }
|
nu-protocol = { version = "0.32.1", path = "../nu-protocol" }
|
||||||
@ -81,7 +82,6 @@ serde_json = "1.0.61"
|
|||||||
serde_urlencoded = "0.7.0"
|
serde_urlencoded = "0.7.0"
|
||||||
serde_yaml = "0.8.16"
|
serde_yaml = "0.8.16"
|
||||||
sha2 = "0.9.3"
|
sha2 = "0.9.3"
|
||||||
shellexpand = "2.1.0"
|
|
||||||
strip-ansi-escapes = "0.1.0"
|
strip-ansi-escapes = "0.1.0"
|
||||||
sxd-document = "0.3.2"
|
sxd-document = "0.3.2"
|
||||||
sxd-xpath = "0.4.2"
|
sxd-xpath = "0.4.2"
|
||||||
|
@ -5,7 +5,6 @@ use nu_test_support::NATIVE_PATH_ENV_VAR;
|
|||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
|
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::ops::Deref;
|
|
||||||
use std::process::{Command, Stdio};
|
use std::process::{Command, Stdio};
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
use std::{borrow::Cow, io::BufReader};
|
use std::{borrow::Cow, io::BufReader};
|
||||||
@ -105,7 +104,7 @@ fn run_with_stdin(
|
|||||||
let process_args = command_args
|
let process_args = command_args
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(arg, _is_literal)| {
|
.map(|(arg, _is_literal)| {
|
||||||
let arg = expand_tilde(arg.deref(), dirs_next::home_dir);
|
let arg = nu_path::expand_tilde_string(Cow::Borrowed(arg));
|
||||||
|
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
{
|
{
|
||||||
@ -126,7 +125,7 @@ fn run_with_stdin(
|
|||||||
if let Some(unquoted) = remove_quotes(&arg) {
|
if let Some(unquoted) = remove_quotes(&arg) {
|
||||||
unquoted.to_string()
|
unquoted.to_string()
|
||||||
} else {
|
} else {
|
||||||
arg.as_ref().to_string()
|
arg.to_string()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -486,15 +485,6 @@ impl Iterator for ChannelReceiver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn expand_tilde<SI: ?Sized, P, HD>(input: &SI, home_dir: HD) -> std::borrow::Cow<str>
|
|
||||||
where
|
|
||||||
SI: AsRef<str>,
|
|
||||||
P: AsRef<std::path::Path>,
|
|
||||||
HD: FnOnce() -> Option<P>,
|
|
||||||
{
|
|
||||||
shellexpand::tilde_with_context(input, home_dir)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn argument_is_quoted(argument: &str) -> bool {
|
fn argument_is_quoted(argument: &str) -> bool {
|
||||||
if argument.len() < 2 {
|
if argument.len() < 2 {
|
||||||
return false;
|
return false;
|
||||||
@ -543,9 +533,7 @@ fn shell_os_paths() -> Vec<std::path::PathBuf> {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{
|
use super::{add_double_quotes, argument_is_quoted, escape_double_quotes, remove_quotes};
|
||||||
add_double_quotes, argument_is_quoted, escape_double_quotes, expand_tilde, remove_quotes,
|
|
||||||
};
|
|
||||||
#[cfg(feature = "which")]
|
#[cfg(feature = "which")]
|
||||||
use super::{run_external_command, InputStream};
|
use super::{run_external_command, InputStream};
|
||||||
|
|
||||||
@ -667,20 +655,4 @@ mod tests {
|
|||||||
assert_eq!(remove_quotes("'andrés'"), Some("andrés"));
|
assert_eq!(remove_quotes("'andrés'"), Some("andrés"));
|
||||||
assert_eq!(remove_quotes(r#""andrés""#), Some("andrés"));
|
assert_eq!(remove_quotes(r#""andrés""#), Some("andrés"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn expands_tilde_if_starts_with_tilde_character() {
|
|
||||||
assert_eq!(
|
|
||||||
expand_tilde("~", || Some(std::path::Path::new("the_path_to_nu_light"))),
|
|
||||||
"the_path_to_nu_light"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn does_not_expand_tilde_if_tilde_is_not_first_character() {
|
|
||||||
assert_eq!(
|
|
||||||
expand_tilde("1~1", || Some(std::path::Path::new("the_path_to_nu_light"))),
|
|
||||||
"1~1"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_engine::filesystem::path::canonicalize;
|
|
||||||
use nu_engine::WholeStreamCommand;
|
use nu_engine::WholeStreamCommand;
|
||||||
|
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
|
use nu_path::canonicalize;
|
||||||
use nu_protocol::{CommandAction, ReturnSuccess, Signature, SyntaxShape, UntaggedValue};
|
use nu_protocol::{CommandAction, ReturnSuccess, Signature, SyntaxShape, UntaggedValue};
|
||||||
use nu_source::Tagged;
|
use nu_source::Tagged;
|
||||||
|
|
||||||
|
@ -2,10 +2,12 @@ use crate::prelude::*;
|
|||||||
use nu_engine::{script, WholeStreamCommand};
|
use nu_engine::{script, WholeStreamCommand};
|
||||||
|
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_parser::expand_path;
|
use nu_path::expand_path;
|
||||||
use nu_protocol::{Signature, SyntaxShape};
|
use nu_protocol::{Signature, SyntaxShape};
|
||||||
use nu_source::Tagged;
|
use nu_source::Tagged;
|
||||||
|
|
||||||
|
use std::{borrow::Cow, path::Path};
|
||||||
|
|
||||||
pub struct Source;
|
pub struct Source;
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
@ -46,7 +48,7 @@ pub fn source(args: CommandArgs) -> Result<ActionStream, ShellError> {
|
|||||||
// Note: this is a special case for setting the context from a command
|
// Note: this is a special case for setting the context from a command
|
||||||
// In this case, if we don't set it now, we'll lose the scope that this
|
// In this case, if we don't set it now, we'll lose the scope that this
|
||||||
// variable should be set into.
|
// variable should be set into.
|
||||||
let contents = std::fs::read_to_string(expand_path(&filename.item).into_owned());
|
let contents = std::fs::read_to_string(&expand_path(Cow::Borrowed(Path::new(&filename.item))));
|
||||||
match contents {
|
match contents {
|
||||||
Ok(contents) => {
|
Ok(contents) => {
|
||||||
let result = script::run_script_standalone(contents, true, &ctx, false);
|
let result = script::run_script_standalone(contents, true, &ctx, false);
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
use super::{operate, PathSubcommandArguments};
|
use super::{operate, PathSubcommandArguments};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
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_path::expand_path;
|
||||||
use nu_protocol::{ColumnPath, Signature, SyntaxShape, UntaggedValue, Value};
|
use nu_protocol::{ColumnPath, Signature, SyntaxShape, UntaggedValue, Value};
|
||||||
use nu_source::Span;
|
use nu_source::Span;
|
||||||
use std::path::Path;
|
use std::{borrow::Cow, path::Path};
|
||||||
|
|
||||||
pub struct PathExpand;
|
pub struct PathExpand;
|
||||||
|
|
||||||
@ -102,13 +101,7 @@ fn action(path: &Path, tag: Tag, args: &PathExpandArguments) -> Value {
|
|||||||
tag.span,
|
tag.span,
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
// "best effort" mode, just expand tilde and resolve single/double dots
|
UntaggedValue::filepath(expand_path(Cow::Borrowed(path))).into_value(tag)
|
||||||
let path = match expand_tilde(path) {
|
|
||||||
Some(expanded) => expanded,
|
|
||||||
None => path.into(),
|
|
||||||
};
|
|
||||||
|
|
||||||
UntaggedValue::filepath(resolve_dots(&path)).into_value(tag)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,3 +43,36 @@ fn expands_path_with_double_dot() {
|
|||||||
assert_eq!(PathBuf::from(actual.out), expected);
|
assert_eq!(PathBuf::from(actual.out), expected);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
mod windows {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn expands_path_with_tilde_backward_slash() {
|
||||||
|
Playground::setup("path_expand_2", |dirs, _| {
|
||||||
|
let actual = nu!(
|
||||||
|
cwd: dirs.test(), pipeline(
|
||||||
|
r#"
|
||||||
|
echo "~\tmp.txt" | path expand
|
||||||
|
"#
|
||||||
|
));
|
||||||
|
|
||||||
|
assert!(!PathBuf::from(actual.out).starts_with("~"));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn win_expands_path_with_tilde_forward_slash() {
|
||||||
|
Playground::setup("path_expand_2", |dirs, _| {
|
||||||
|
let actual = nu!(
|
||||||
|
cwd: dirs.test(), pipeline(
|
||||||
|
r#"
|
||||||
|
echo "~/tmp.txt" | path expand
|
||||||
|
"#
|
||||||
|
));
|
||||||
|
|
||||||
|
assert!(!PathBuf::from(actual.out).starts_with("~"));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -13,6 +13,7 @@ doctest = false
|
|||||||
nu-data = { version = "0.32.1", path = "../nu-data" }
|
nu-data = { version = "0.32.1", path = "../nu-data" }
|
||||||
nu-errors = { version = "0.32.1", path = "../nu-errors" }
|
nu-errors = { version = "0.32.1", path = "../nu-errors" }
|
||||||
nu-parser = { version = "0.32.1", path = "../nu-parser" }
|
nu-parser = { version = "0.32.1", path = "../nu-parser" }
|
||||||
|
nu-path = { version = "0.32.1", path = "../nu-path" }
|
||||||
nu-protocol = { version = "0.32.1", path = "../nu-protocol" }
|
nu-protocol = { version = "0.32.1", path = "../nu-protocol" }
|
||||||
nu-source = { version = "0.32.1", path = "../nu-source" }
|
nu-source = { version = "0.32.1", path = "../nu-source" }
|
||||||
nu-test-support = { version = "0.32.1", path = "../nu-test-support" }
|
nu-test-support = { version = "0.32.1", path = "../nu-test-support" }
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
use std::path::PathBuf;
|
use std::borrow::Cow;
|
||||||
|
use std::path::{is_separator, Path, PathBuf};
|
||||||
|
|
||||||
use super::matchers::Matcher;
|
use super::matchers::Matcher;
|
||||||
use crate::{Completer, CompletionContext, Suggestion};
|
use crate::{Completer, CompletionContext, Suggestion};
|
||||||
@ -7,6 +8,7 @@ const SEP: char = std::path::MAIN_SEPARATOR;
|
|||||||
|
|
||||||
pub struct PathCompleter;
|
pub struct PathCompleter;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct PathSuggestion {
|
pub struct PathSuggestion {
|
||||||
pub(crate) path: PathBuf,
|
pub(crate) path: PathBuf,
|
||||||
pub(crate) suggestion: Suggestion,
|
pub(crate) suggestion: Suggestion,
|
||||||
@ -14,27 +16,23 @@ pub struct PathSuggestion {
|
|||||||
|
|
||||||
impl PathCompleter {
|
impl PathCompleter {
|
||||||
pub fn path_suggestions(&self, partial: &str, matcher: &dyn Matcher) -> Vec<PathSuggestion> {
|
pub fn path_suggestions(&self, partial: &str, matcher: &dyn Matcher) -> Vec<PathSuggestion> {
|
||||||
let expanded = nu_parser::expand_ndots(partial);
|
let (base_dir_name, partial) = {
|
||||||
let expanded = expanded.replace(std::path::is_separator, &SEP.to_string());
|
// If partial is only a word we want to search in the current dir
|
||||||
let expanded: &str = expanded.as_ref();
|
let (base, rest) = partial.rsplit_once(is_separator).unwrap_or((".", partial));
|
||||||
|
// On windows, this standardizes paths to use \
|
||||||
|
let mut base = base.replace(is_separator, &SEP.to_string());
|
||||||
|
|
||||||
let (base_dir_name, partial) = match expanded.rfind(SEP) {
|
// rsplit_once removes the separator
|
||||||
Some(pos) => expanded.split_at(pos + SEP.len_utf8()),
|
base.push(SEP);
|
||||||
None => ("", expanded),
|
(base, rest)
|
||||||
};
|
};
|
||||||
|
|
||||||
let base_dir = if base_dir_name.is_empty() {
|
let base_dir = nu_path::expand_path(Cow::Borrowed(Path::new(&base_dir_name)));
|
||||||
PathBuf::from(".")
|
// This check is here as base_dir.read_dir() with base_dir == "" will open the current dir
|
||||||
} else {
|
// which we don't want in this case (if we did, base_dir would already be ".")
|
||||||
let home_prefix = format!("~{}", SEP);
|
if base_dir == Path::new("") {
|
||||||
if base_dir_name.starts_with(&home_prefix) {
|
return Vec::new();
|
||||||
let mut home_dir = dirs_next::home_dir().unwrap_or_else(|| PathBuf::from("~"));
|
}
|
||||||
home_dir.push(&base_dir_name[2..]);
|
|
||||||
home_dir
|
|
||||||
} else {
|
|
||||||
PathBuf::from(base_dir_name)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Ok(result) = base_dir.read_dir() {
|
if let Ok(result) = base_dir.read_dir() {
|
||||||
result
|
result
|
||||||
@ -42,10 +40,10 @@ impl PathCompleter {
|
|||||||
entry.ok().and_then(|entry| {
|
entry.ok().and_then(|entry| {
|
||||||
let mut file_name = entry.file_name().to_string_lossy().into_owned();
|
let mut file_name = entry.file_name().to_string_lossy().into_owned();
|
||||||
if matcher.matches(partial, file_name.as_str()) {
|
if matcher.matches(partial, file_name.as_str()) {
|
||||||
let mut path = format!("{}{}", base_dir_name, file_name);
|
let mut path = format!("{}{}", &base_dir_name, file_name);
|
||||||
if entry.file_type().map(|ft| ft.is_dir()).unwrap_or(false) {
|
if entry.file_type().map(|ft| ft.is_dir()).unwrap_or(false) {
|
||||||
path.push(std::path::MAIN_SEPARATOR);
|
path.push(SEP);
|
||||||
file_name.push(std::path::MAIN_SEPARATOR);
|
file_name.push(SEP);
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(PathSuggestion {
|
Some(PathSuggestion {
|
||||||
|
@ -17,6 +17,7 @@ nu-stream = { version="0.32.1", path="../nu-stream" }
|
|||||||
nu-value-ext = { version="0.32.1", path="../nu-value-ext" }
|
nu-value-ext = { version="0.32.1", path="../nu-value-ext" }
|
||||||
nu-ansi-term = { version="0.32.1", path="../nu-ansi-term" }
|
nu-ansi-term = { version="0.32.1", path="../nu-ansi-term" }
|
||||||
nu-test-support = { version="0.32.1", path="../nu-test-support" }
|
nu-test-support = { version="0.32.1", path="../nu-test-support" }
|
||||||
|
nu-path = { version = "0.32.1", path = "../nu-path" }
|
||||||
|
|
||||||
trash = { version="1.3.0", optional=true }
|
trash = { version="1.3.0", optional=true }
|
||||||
which = { version="4.0.2", optional=true }
|
which = { version="4.0.2", optional=true }
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
use crate::filesystem::path::canonicalize;
|
|
||||||
use crate::filesystem::utils::FileStructure;
|
use crate::filesystem::utils::FileStructure;
|
||||||
use crate::maybe_text_codec::{MaybeTextCodec, StringOrBinary};
|
use crate::maybe_text_codec::{MaybeTextCodec, StringOrBinary};
|
||||||
use crate::shell::shell_args::{CdArgs, CopyArgs, LsArgs, MkdirArgs, MvArgs, RemoveArgs};
|
use crate::shell::shell_args::{CdArgs, CopyArgs, LsArgs, MkdirArgs, MvArgs, RemoveArgs};
|
||||||
@ -10,6 +9,7 @@ use crate::{
|
|||||||
};
|
};
|
||||||
use encoding_rs::Encoding;
|
use encoding_rs::Encoding;
|
||||||
use nu_data::config::LocalConfigDiff;
|
use nu_data::config::LocalConfigDiff;
|
||||||
|
use nu_path::canonicalize;
|
||||||
use nu_protocol::{CommandAction, ConfigPath, TaggedDictBuilder, Value};
|
use nu_protocol::{CommandAction, ConfigPath, TaggedDictBuilder, Value};
|
||||||
use nu_source::{Span, Tag};
|
use nu_source::{Span, Tag};
|
||||||
use nu_stream::{ActionStream, Interruptible, IntoActionStream, OutputStream};
|
use nu_stream::{ActionStream, Interruptible, IntoActionStream, OutputStream};
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
pub(crate) mod dir_info;
|
pub(crate) mod dir_info;
|
||||||
pub mod filesystem_shell;
|
pub mod filesystem_shell;
|
||||||
pub mod path;
|
|
||||||
pub(crate) mod utils;
|
pub(crate) mod utils;
|
||||||
|
@ -1,178 +0,0 @@
|
|||||||
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>,
|
|
||||||
Q: AsRef<Path>,
|
|
||||||
{
|
|
||||||
let path = if path.as_ref() == Path::new(".") {
|
|
||||||
// Joining a Path with '.' appends a '.' at the end, making the prompt
|
|
||||||
// more ugly - so we don't do anything, which should result in an equal
|
|
||||||
// path on all supported systems.
|
|
||||||
relative_to.as_ref().to_owned()
|
|
||||||
} else if path.as_ref().starts_with("~") {
|
|
||||||
let expanded_path = expand_tilde(path.as_ref());
|
|
||||||
match expanded_path {
|
|
||||||
Some(p) => p,
|
|
||||||
_ => path.as_ref().to_owned(),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
relative_to.as_ref().join(path)
|
|
||||||
};
|
|
||||||
|
|
||||||
let (relative_to, path) = {
|
|
||||||
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(),
|
|
||||||
components.iter().collect::<PathBuf>(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
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
|
|
||||||
};
|
|
||||||
|
|
||||||
dunce::simplified(&path).to_path_buf()
|
|
||||||
}
|
|
||||||
|
|
||||||
// borrowed from here https://stackoverflow.com/questions/54267608/expand-tilde-in-rust-path-idiomatically
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
|
|
||||||
if p == Path::new("~") {
|
|
||||||
return dirs_next::home_dir();
|
|
||||||
}
|
|
||||||
|
|
||||||
dirs_next::home_dir().map(|mut h| {
|
|
||||||
if h == Path::new("/") {
|
|
||||||
// Corner case: `h` root directory;
|
|
||||||
// don't prepend extra `/`, just drop the tilde.
|
|
||||||
p.strip_prefix("~")
|
|
||||||
.expect("cannot strip ~ prefix")
|
|
||||||
.to_path_buf()
|
|
||||||
} else {
|
|
||||||
h.push(p.strip_prefix("~/").expect("cannot strip ~/ prefix"));
|
|
||||||
h
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn canonicalize<P, Q>(relative_to: P, path: Q) -> io::Result<PathBuf>
|
|
||||||
where
|
|
||||||
P: AsRef<Path>,
|
|
||||||
Q: AsRef<Path>,
|
|
||||||
{
|
|
||||||
let absolutized = absolutize(&relative_to, path);
|
|
||||||
let path = match std::fs::read_link(&absolutized) {
|
|
||||||
Ok(resolved) => {
|
|
||||||
let parent = absolutized.parent().unwrap_or(&absolutized);
|
|
||||||
absolutize(parent, resolved)
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(e) => {
|
|
||||||
if absolutized.exists() {
|
|
||||||
absolutized
|
|
||||||
} else {
|
|
||||||
return Err(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(dunce::simplified(&path).to_path_buf())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use std::io;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn absolutize_two_dots() {
|
|
||||||
let relative_to = Path::new("/foo/bar");
|
|
||||||
let path = Path::new("..");
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
PathBuf::from("/foo"), // missing path
|
|
||||||
absolutize(relative_to, path)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn absolutize_with_curdir() {
|
|
||||||
let relative_to = Path::new("/foo");
|
|
||||||
let path = Path::new("./bar/./baz");
|
|
||||||
|
|
||||||
assert!(!absolutize(relative_to, path)
|
|
||||||
.to_str()
|
|
||||||
.unwrap()
|
|
||||||
.contains('.'));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn canonicalize_should_succeed() -> io::Result<()> {
|
|
||||||
let relative_to = Path::new("/foo/bar");
|
|
||||||
let path = Path::new("../..");
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
PathBuf::from("/"), // existing path
|
|
||||||
canonicalize(relative_to, path)?,
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn canonicalize_should_fail() {
|
|
||||||
let relative_to = Path::new("/foo/bar/baz"); // '/foo' is missing
|
|
||||||
let path = Path::new("../..");
|
|
||||||
|
|
||||||
assert!(canonicalize(relative_to, path).is_err());
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +1,5 @@
|
|||||||
use crate::filesystem::path::canonicalize;
|
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
|
use nu_path::canonicalize;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
@ -29,7 +29,6 @@ pub use crate::evaluation_context::EvaluationContext;
|
|||||||
pub use crate::example::Example;
|
pub use crate::example::Example;
|
||||||
pub use crate::filesystem::dir_info::{DirBuilder, DirInfo, FileInfo};
|
pub use crate::filesystem::dir_info::{DirBuilder, DirInfo, FileInfo};
|
||||||
pub use crate::filesystem::filesystem_shell::FilesystemShell;
|
pub use crate::filesystem::filesystem_shell::FilesystemShell;
|
||||||
pub use crate::filesystem::path;
|
|
||||||
pub use crate::from_value::FromValue;
|
pub use crate::from_value::FromValue;
|
||||||
pub use crate::maybe_text_codec::{BufCodecReader, MaybeTextCodec, StringOrBinary};
|
pub use crate::maybe_text_codec::{BufCodecReader, MaybeTextCodec, StringOrBinary};
|
||||||
pub use crate::print::maybe_print_errors;
|
pub use crate::print::maybe_print_errors;
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
use crate::{
|
use crate::{evaluate::internal::InternalIterator, maybe_print_errors, run_block, shell::CdArgs};
|
||||||
evaluate::internal::InternalIterator, maybe_print_errors, path::canonicalize, run_block,
|
|
||||||
shell::CdArgs,
|
|
||||||
};
|
|
||||||
use crate::{BufCodecReader, MaybeTextCodec, StringOrBinary};
|
use crate::{BufCodecReader, MaybeTextCodec, StringOrBinary};
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
|
use nu_path::canonicalize;
|
||||||
use nu_protocol::hir::{
|
use nu_protocol::hir::{
|
||||||
Call, ClassifiedCommand, Expression, ExternalRedirection, InternalCommand, Literal,
|
Call, ClassifiedCommand, Expression, ExternalRedirection, InternalCommand, Literal,
|
||||||
NamedArguments, SpannedExpression,
|
NamedArguments, SpannedExpression,
|
||||||
|
@ -6,8 +6,6 @@ license = "MIT"
|
|||||||
name = "nu-parser"
|
name = "nu-parser"
|
||||||
version = "0.32.1"
|
version = "0.32.1"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bigdecimal = { version = "0.2.0", features = ["serde"] }
|
bigdecimal = { version = "0.2.0", features = ["serde"] }
|
||||||
codespan-reporting = "0.11.0"
|
codespan-reporting = "0.11.0"
|
||||||
@ -18,12 +16,12 @@ log = "0.4"
|
|||||||
num-bigint = { version = "0.3.1", features = ["serde"] }
|
num-bigint = { version = "0.3.1", features = ["serde"] }
|
||||||
num-traits = "0.2.14"
|
num-traits = "0.2.14"
|
||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
shellexpand = "2.1.0"
|
|
||||||
itertools = "0.10.0"
|
itertools = "0.10.0"
|
||||||
smart-default = "0.6.0"
|
smart-default = "0.6.0"
|
||||||
|
|
||||||
dunce = "1.0.1"
|
dunce = "1.0.1"
|
||||||
nu-errors = { version = "0.32.1", path = "../nu-errors" }
|
nu-errors = { version = "0.32.1", path = "../nu-errors" }
|
||||||
|
nu-path = { version = "0.32.1", path = "../nu-path" }
|
||||||
nu-protocol = { version = "0.32.1", path = "../nu-protocol" }
|
nu-protocol = { version = "0.32.1", path = "../nu-protocol" }
|
||||||
nu-source = { version = "0.32.1", path = "../nu-source" }
|
nu-source = { version = "0.32.1", path = "../nu-source" }
|
||||||
nu-test-support = { version = "0.32.1", path = "../nu-test-support" }
|
nu-test-support = { version = "0.32.1", path = "../nu-test-support" }
|
||||||
|
@ -7,7 +7,6 @@ mod errors;
|
|||||||
mod flag;
|
mod flag;
|
||||||
mod lex;
|
mod lex;
|
||||||
mod parse;
|
mod parse;
|
||||||
mod path;
|
|
||||||
mod scope;
|
mod scope;
|
||||||
mod shapes;
|
mod shapes;
|
||||||
mod signature;
|
mod signature;
|
||||||
@ -15,8 +14,6 @@ mod signature;
|
|||||||
pub use lex::lexer::{lex, parse_block};
|
pub use lex::lexer::{lex, parse_block};
|
||||||
pub use lex::tokens::{LiteBlock, LiteCommand, LiteGroup, LitePipeline};
|
pub use lex::tokens::{LiteBlock, LiteCommand, LiteGroup, LitePipeline};
|
||||||
pub use parse::{classify_block, garbage, parse, parse_full_column_path, parse_math_expression};
|
pub use parse::{classify_block, garbage, parse, parse_full_column_path, parse_math_expression};
|
||||||
pub use path::expand_ndots;
|
|
||||||
pub use path::expand_path;
|
|
||||||
pub use scope::ParserScope;
|
pub use scope::ParserScope;
|
||||||
pub use shapes::shapes;
|
pub use shapes::shapes;
|
||||||
pub use signature::{Signature, SignatureRegistry};
|
pub use signature::{Signature, SignatureRegistry};
|
||||||
|
@ -1,9 +1,14 @@
|
|||||||
use std::{path::Path, sync::Arc};
|
use std::borrow::Cow;
|
||||||
|
use std::{
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
|
||||||
use bigdecimal::BigDecimal;
|
use bigdecimal::BigDecimal;
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
use log::trace;
|
use log::trace;
|
||||||
use nu_errors::{ArgumentError, ParseError};
|
use nu_errors::{ArgumentError, ParseError};
|
||||||
|
use nu_path::{expand_path, expand_path_string};
|
||||||
use nu_protocol::hir::{
|
use nu_protocol::hir::{
|
||||||
self, Binary, Block, ClassifiedCommand, Expression, ExternalRedirection, Flag, FlagKind, Group,
|
self, Binary, Block, ClassifiedCommand, Expression, ExternalRedirection, Flag, FlagKind, Group,
|
||||||
InternalCommand, Member, NamedArguments, Operator, Pipeline, RangeOperator, SpannedExpression,
|
InternalCommand, Member, NamedArguments, Operator, Pipeline, RangeOperator, SpannedExpression,
|
||||||
@ -13,6 +18,7 @@ use nu_protocol::{NamedType, PositionalType, Signature, SyntaxShape, UnspannedPa
|
|||||||
use nu_source::{HasSpan, Span, Spanned, SpannedItem};
|
use nu_source::{HasSpan, Span, Spanned, SpannedItem};
|
||||||
use num_bigint::BigInt;
|
use num_bigint::BigInt;
|
||||||
|
|
||||||
|
use crate::parse::def::parse_parameter;
|
||||||
use crate::{
|
use crate::{
|
||||||
lex::lexer::{lex, parse_block},
|
lex::lexer::{lex, parse_block},
|
||||||
ParserScope,
|
ParserScope,
|
||||||
@ -24,7 +30,6 @@ use crate::{
|
|||||||
},
|
},
|
||||||
parse::def::lex_split_baseline_tokens_on,
|
parse::def::lex_split_baseline_tokens_on,
|
||||||
};
|
};
|
||||||
use crate::{parse::def::parse_parameter, path::expand_path};
|
|
||||||
|
|
||||||
use self::{
|
use self::{
|
||||||
def::{parse_definition, parse_definition_prototype},
|
def::{parse_definition, parse_definition_prototype},
|
||||||
@ -67,7 +72,7 @@ pub fn parse_simple_column_path(
|
|||||||
output.push(Member::Int(row_number, part_span));
|
output.push(Member::Int(row_number, part_span));
|
||||||
} else {
|
} else {
|
||||||
let trimmed = trim_quotes(¤t_part);
|
let trimmed = trim_quotes(¤t_part);
|
||||||
output.push(Member::Bare(trimmed.clone().spanned(part_span)));
|
output.push(Member::Bare(trimmed.spanned(part_span)));
|
||||||
}
|
}
|
||||||
current_part.clear();
|
current_part.clear();
|
||||||
// Note: I believe this is safe because of the delimiter we're using,
|
// Note: I believe this is safe because of the delimiter we're using,
|
||||||
@ -944,8 +949,8 @@ fn parse_arg(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
SyntaxShape::GlobPattern => {
|
SyntaxShape::GlobPattern => {
|
||||||
let trimmed = trim_quotes(&lite_arg.item);
|
let trimmed = Cow::Owned(trim_quotes(&lite_arg.item));
|
||||||
let expanded = expand_path(&trimmed).to_string();
|
let expanded = expand_path_string(trimmed).to_string();
|
||||||
(
|
(
|
||||||
SpannedExpression::new(Expression::glob_pattern(expanded), lite_arg.span),
|
SpannedExpression::new(Expression::glob_pattern(expanded), lite_arg.span),
|
||||||
None,
|
None,
|
||||||
@ -961,10 +966,10 @@ fn parse_arg(
|
|||||||
SyntaxShape::Duration => parse_duration(lite_arg),
|
SyntaxShape::Duration => parse_duration(lite_arg),
|
||||||
SyntaxShape::FilePath => {
|
SyntaxShape::FilePath => {
|
||||||
let trimmed = trim_quotes(&lite_arg.item);
|
let trimmed = trim_quotes(&lite_arg.item);
|
||||||
let expanded = expand_path(&trimmed).to_string();
|
let path = PathBuf::from(trimmed);
|
||||||
let path = Path::new(&expanded);
|
let expanded = expand_path(Cow::Owned(path)).to_path_buf();
|
||||||
(
|
(
|
||||||
SpannedExpression::new(Expression::FilePath(path.to_path_buf()), lite_arg.span),
|
SpannedExpression::new(Expression::FilePath(expanded), lite_arg.span),
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -1647,8 +1652,8 @@ fn parse_external_call(
|
|||||||
) -> (Option<ClassifiedCommand>, Option<ParseError>) {
|
) -> (Option<ClassifiedCommand>, Option<ParseError>) {
|
||||||
let mut error = None;
|
let mut error = None;
|
||||||
let name = lite_cmd.parts[0].clone().map(|v| {
|
let name = lite_cmd.parts[0].clone().map(|v| {
|
||||||
let trimmed = trim_quotes(&v);
|
let trimmed = Cow::Owned(trim_quotes(&v));
|
||||||
expand_path(&trimmed).to_string()
|
expand_path_string(trimmed).to_string()
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut args = vec![];
|
let mut args = vec![];
|
||||||
@ -1877,9 +1882,9 @@ fn parse_call(
|
|||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if let Ok(contents) =
|
if let Ok(contents) = std::fs::read_to_string(&expand_path(Cow::Borrowed(Path::new(
|
||||||
std::fs::read_to_string(expand_path(&lite_cmd.parts[1].item).into_owned())
|
&lite_cmd.parts[1].item,
|
||||||
{
|
)))) {
|
||||||
let _ = parse(&contents, 0, scope);
|
let _ = parse(&contents, 0, scope);
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
|
@ -1,180 +0,0 @@
|
|||||||
use std::borrow::Cow;
|
|
||||||
|
|
||||||
const EXPAND_STR: &str = if cfg!(windows) { r"..\" } else { "../" };
|
|
||||||
|
|
||||||
fn handle_dots_push(string: &mut String, count: u8) {
|
|
||||||
if count < 1 {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if count == 1 {
|
|
||||||
string.push('.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for _ in 0..(count - 1) {
|
|
||||||
string.push_str(EXPAND_STR);
|
|
||||||
}
|
|
||||||
|
|
||||||
string.pop(); // remove last '/'
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn expand_ndots(path: &str) -> Cow<'_, str> {
|
|
||||||
// helpers
|
|
||||||
#[cfg(windows)]
|
|
||||||
fn is_separator(c: char) -> bool {
|
|
||||||
// AFAIK, Windows can have both \ and / as path components separators
|
|
||||||
(c == '/') || (c == '\\')
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(windows))]
|
|
||||||
fn is_separator(c: char) -> bool {
|
|
||||||
c == '/'
|
|
||||||
}
|
|
||||||
|
|
||||||
// find if we need to expand any >2 dot paths and early exit if not
|
|
||||||
let mut dots_count = 0u8;
|
|
||||||
let ndots_present = {
|
|
||||||
for chr in path.chars() {
|
|
||||||
if chr == '.' {
|
|
||||||
dots_count += 1;
|
|
||||||
} else {
|
|
||||||
if is_separator(chr) && (dots_count > 2) {
|
|
||||||
// this path component had >2 dots
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
dots_count = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dots_count > 2
|
|
||||||
};
|
|
||||||
|
|
||||||
if !ndots_present {
|
|
||||||
return path.into();
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut dots_count = 0u8;
|
|
||||||
let mut expanded = String::new();
|
|
||||||
for chr in path.chars() {
|
|
||||||
if chr == '.' {
|
|
||||||
dots_count += 1;
|
|
||||||
} else {
|
|
||||||
if is_separator(chr) {
|
|
||||||
// check for dots expansion only at path component boundaries
|
|
||||||
handle_dots_push(&mut expanded, dots_count);
|
|
||||||
dots_count = 0;
|
|
||||||
} else {
|
|
||||||
// got non-dot within path component => do not expand any dots
|
|
||||||
while dots_count > 0 {
|
|
||||||
expanded.push('.');
|
|
||||||
dots_count -= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
expanded.push(chr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handle_dots_push(&mut expanded, dots_count);
|
|
||||||
|
|
||||||
expanded.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn expand_path<'a>(path: &'a str) -> Cow<'a, str> {
|
|
||||||
let tilde_expansion: Cow<'a, str> = shellexpand::tilde(path);
|
|
||||||
let ndots_expansion: Cow<'a, str> = match tilde_expansion {
|
|
||||||
Cow::Borrowed(b) => expand_ndots(b),
|
|
||||||
Cow::Owned(o) => expand_ndots(&o).to_string().into(),
|
|
||||||
};
|
|
||||||
|
|
||||||
ndots_expansion
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
// common tests
|
|
||||||
#[test]
|
|
||||||
fn string_without_ndots() {
|
|
||||||
assert_eq!("../hola", &expand_ndots("../hola").to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn string_with_three_ndots_and_chars() {
|
|
||||||
assert_eq!("a...b", &expand_ndots("a...b").to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn string_with_two_ndots_and_chars() {
|
|
||||||
assert_eq!("a..b", &expand_ndots("a..b").to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn string_with_one_dot_and_chars() {
|
|
||||||
assert_eq!("a.b", &expand_ndots("a.b").to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Windows tests
|
|
||||||
#[cfg(windows)]
|
|
||||||
#[test]
|
|
||||||
fn string_with_three_ndots() {
|
|
||||||
assert_eq!(r"..\..", &expand_ndots("...").to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
|
||||||
#[test]
|
|
||||||
fn string_with_mixed_ndots_and_chars() {
|
|
||||||
assert_eq!(
|
|
||||||
r"a...b/./c..d/../e.f/..\..\..//.",
|
|
||||||
&expand_ndots("a...b/./c..d/../e.f/....//.").to_string()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
|
||||||
#[test]
|
|
||||||
fn string_with_three_ndots_and_final_slash() {
|
|
||||||
assert_eq!(r"..\../", &expand_ndots(".../").to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
|
||||||
#[test]
|
|
||||||
fn string_with_three_ndots_and_garbage() {
|
|
||||||
assert_eq!(
|
|
||||||
r"ls ..\../ garbage.*[",
|
|
||||||
&expand_ndots("ls .../ garbage.*[").to_string(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// non-Windows tests
|
|
||||||
#[cfg(not(windows))]
|
|
||||||
#[test]
|
|
||||||
fn string_with_three_ndots() {
|
|
||||||
assert_eq!(r"../..", &expand_ndots("...").to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(windows))]
|
|
||||||
#[test]
|
|
||||||
fn string_with_mixed_ndots_and_chars() {
|
|
||||||
assert_eq!(
|
|
||||||
"a...b/./c..d/../e.f/../../..//.",
|
|
||||||
&expand_ndots("a...b/./c..d/../e.f/....//.").to_string()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(windows))]
|
|
||||||
#[test]
|
|
||||||
fn string_with_three_ndots_and_final_slash() {
|
|
||||||
assert_eq!("../../", &expand_ndots(".../").to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(windows))]
|
|
||||||
#[test]
|
|
||||||
fn string_with_three_ndots_and_garbage() {
|
|
||||||
assert_eq!(
|
|
||||||
"ls ../../ garbage.*[",
|
|
||||||
&expand_ndots("ls .../ garbage.*[").to_string(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
11
crates/nu-path/Cargo.toml
Normal file
11
crates/nu-path/Cargo.toml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
[package]
|
||||||
|
authors = ["The Nu Project Contributors"]
|
||||||
|
description = "Nushell parser"
|
||||||
|
edition = "2018"
|
||||||
|
license = "MIT"
|
||||||
|
name = "nu-path"
|
||||||
|
version = "0.32.1"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
dirs-next = "2.0.0"
|
||||||
|
dunce = "1.0.1"
|
530
crates/nu-path/src/lib.rs
Normal file
530
crates/nu-path/src/lib.rs
Normal file
@ -0,0 +1,530 @@
|
|||||||
|
use std::borrow::Cow;
|
||||||
|
use std::io;
|
||||||
|
use std::path::{Component, Path, PathBuf};
|
||||||
|
|
||||||
|
// Utility for applying a function that can only be called on the borrowed type of the Cow
|
||||||
|
// and also returns a ref. If the Cow is a borrow, we can return the same borrow but an
|
||||||
|
// owned value needs extra handling because the returned valued has to be owned as well
|
||||||
|
pub fn cow_map_by_ref<B, O, F>(c: Cow<'_, B>, f: F) -> Cow<'_, B>
|
||||||
|
where
|
||||||
|
B: ToOwned<Owned = O> + ?Sized,
|
||||||
|
O: AsRef<B>,
|
||||||
|
F: FnOnce(&B) -> &B,
|
||||||
|
{
|
||||||
|
match c {
|
||||||
|
Cow::Borrowed(b) => Cow::Borrowed(f(b)),
|
||||||
|
Cow::Owned(o) => Cow::Owned(f(o.as_ref()).to_owned()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Utility for applying a function over Cow<'a, Path> over a Cow<'a, str> while avoiding unnecessary conversions
|
||||||
|
fn cow_map_str_path<'a, F>(c: Cow<'a, str>, f: F) -> Cow<'a, str>
|
||||||
|
where
|
||||||
|
F: FnOnce(Cow<'a, Path>) -> Cow<'a, Path>,
|
||||||
|
{
|
||||||
|
let ret = match c {
|
||||||
|
Cow::Borrowed(b) => f(Cow::Borrowed(Path::new(b))),
|
||||||
|
Cow::Owned(o) => f(Cow::Owned(PathBuf::from(o))),
|
||||||
|
};
|
||||||
|
|
||||||
|
match ret {
|
||||||
|
Cow::Borrowed(expanded) => expanded.to_string_lossy(),
|
||||||
|
Cow::Owned(expanded) => Cow::Owned(expanded.to_string_lossy().to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Utility for applying a function over Cow<'a, str> over a Cow<'a, Path> while avoiding unnecessary conversions
|
||||||
|
fn cow_map_path_str<'a, F>(c: Cow<'a, Path>, f: F) -> Cow<'a, Path>
|
||||||
|
where
|
||||||
|
F: FnOnce(Cow<'a, str>) -> Cow<'a, str>,
|
||||||
|
{
|
||||||
|
let ret = match c {
|
||||||
|
Cow::Borrowed(path) => f(path.to_string_lossy()),
|
||||||
|
Cow::Owned(buf) => f(Cow::Owned(buf.to_string_lossy().to_string())),
|
||||||
|
};
|
||||||
|
|
||||||
|
match ret {
|
||||||
|
Cow::Borrowed(expanded) => Cow::Borrowed(Path::new(expanded)),
|
||||||
|
Cow::Owned(expanded) => Cow::Owned(PathBuf::from(expanded)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const EXPAND_STR: &str = if cfg!(windows) { r"..\" } else { "../" };
|
||||||
|
fn handle_dots_push(string: &mut String, count: u8) {
|
||||||
|
if count < 1 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if count == 1 {
|
||||||
|
string.push('.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for _ in 0..(count - 1) {
|
||||||
|
string.push_str(EXPAND_STR);
|
||||||
|
}
|
||||||
|
|
||||||
|
string.pop(); // remove last '/'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expands any occurence of more than two dots into a sequence of ../ (or ..\ on windows), e.g.
|
||||||
|
// ... into ../..
|
||||||
|
// .... into ../../../
|
||||||
|
fn expand_ndots_string(path: Cow<'_, str>) -> Cow<'_, str> {
|
||||||
|
use std::path::is_separator;
|
||||||
|
// find if we need to expand any >2 dot paths and early exit if not
|
||||||
|
let mut dots_count = 0u8;
|
||||||
|
let ndots_present = {
|
||||||
|
for chr in path.chars() {
|
||||||
|
if chr == '.' {
|
||||||
|
dots_count += 1;
|
||||||
|
} else {
|
||||||
|
if is_separator(chr) && (dots_count > 2) {
|
||||||
|
// this path component had >2 dots
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
dots_count = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dots_count > 2
|
||||||
|
};
|
||||||
|
|
||||||
|
if !ndots_present {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut dots_count = 0u8;
|
||||||
|
let mut expanded = String::new();
|
||||||
|
for chr in path.chars() {
|
||||||
|
if chr == '.' {
|
||||||
|
dots_count += 1;
|
||||||
|
} else {
|
||||||
|
if is_separator(chr) {
|
||||||
|
// check for dots expansion only at path component boundaries
|
||||||
|
handle_dots_push(&mut expanded, dots_count);
|
||||||
|
dots_count = 0;
|
||||||
|
} else {
|
||||||
|
// got non-dot within path component => do not expand any dots
|
||||||
|
while dots_count > 0 {
|
||||||
|
expanded.push('.');
|
||||||
|
dots_count -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expanded.push(chr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handle_dots_push(&mut expanded, dots_count);
|
||||||
|
|
||||||
|
expanded.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expands any occurence of more than two dots into a sequence of ../ (or ..\ on windows), e.g.
|
||||||
|
// ... into ../..
|
||||||
|
// .... into ../../../
|
||||||
|
fn expand_ndots(path: Cow<'_, Path>) -> Cow<'_, Path> {
|
||||||
|
cow_map_path_str(path, expand_ndots_string)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn absolutize<P, Q>(relative_to: P, path: Q) -> PathBuf
|
||||||
|
where
|
||||||
|
P: AsRef<Path>,
|
||||||
|
Q: AsRef<Path>,
|
||||||
|
{
|
||||||
|
let path = if path.as_ref() == Path::new(".") {
|
||||||
|
// Joining a Path with '.' appends a '.' at the end, making the prompt
|
||||||
|
// more ugly - so we don't do anything, which should result in an equal
|
||||||
|
// path on all supported systems.
|
||||||
|
relative_to.as_ref().to_owned()
|
||||||
|
} else if path.as_ref().starts_with("~") {
|
||||||
|
expand_tilde(Cow::Borrowed(path.as_ref())).to_path_buf()
|
||||||
|
} else {
|
||||||
|
relative_to.as_ref().join(path)
|
||||||
|
};
|
||||||
|
|
||||||
|
let (relative_to, path) = {
|
||||||
|
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(),
|
||||||
|
components.iter().collect::<PathBuf>(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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
|
||||||
|
};
|
||||||
|
|
||||||
|
dunce::simplified(&path).to_path_buf()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn canonicalize<P, Q>(relative_to: P, path: Q) -> io::Result<PathBuf>
|
||||||
|
where
|
||||||
|
P: AsRef<Path>,
|
||||||
|
Q: AsRef<Path>,
|
||||||
|
{
|
||||||
|
let absolutized = absolutize(&relative_to, path);
|
||||||
|
let path = match std::fs::read_link(&absolutized) {
|
||||||
|
Ok(resolved) => {
|
||||||
|
let parent = absolutized.parent().unwrap_or(&absolutized);
|
||||||
|
absolutize(parent, resolved)
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(e) => {
|
||||||
|
if absolutized.exists() {
|
||||||
|
absolutized
|
||||||
|
} else {
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(dunce::simplified(&path).to_path_buf())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expansion logic lives here to enable testing without depending on dirs-next
|
||||||
|
fn expand_tilde_with(path: Cow<'_, Path>, home: Option<PathBuf>) -> Cow<'_, Path> {
|
||||||
|
if !path.starts_with("~") {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
match home {
|
||||||
|
None => path,
|
||||||
|
Some(mut h) => {
|
||||||
|
if h == Path::new("/") {
|
||||||
|
// Corner case: `h` root directory;
|
||||||
|
// don't prepend extra `/`, just drop the tilde.
|
||||||
|
cow_map_by_ref(path, |p: &Path| {
|
||||||
|
p.strip_prefix("~").expect("cannot strip ~ prefix")
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
h.push(path.strip_prefix("~/").expect("cannot strip ~/ prefix"));
|
||||||
|
Cow::Owned(h)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn expand_tilde(path: Cow<'_, Path>) -> Cow<'_, Path> {
|
||||||
|
expand_tilde_with(path, dirs_next::home_dir())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn expand_tilde_string(path: Cow<'_, str>) -> Cow<'_, str> {
|
||||||
|
cow_map_str_path(path, expand_tilde)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove "." and ".." in a path. Prefix ".." are not removed as we don't have access to the
|
||||||
|
// current dir. This is merely 'string manipulation'. Does not handle "...+", see expand_ndots for that
|
||||||
|
pub fn resolve_dots(path: Cow<'_, Path>) -> Cow<'_, Path> {
|
||||||
|
debug_assert!(!path.components().any(|c| std::matches!(c, Component::Normal(os_str) if os_str.to_string_lossy().starts_with("..."))), "Unexpected ndots!");
|
||||||
|
if !path
|
||||||
|
.components()
|
||||||
|
.any(|c| std::matches!(c, Component::CurDir | Component::ParentDir))
|
||||||
|
{
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut result = PathBuf::with_capacity(path.as_os_str().len());
|
||||||
|
|
||||||
|
// Only pop/skip path elements if the previous one was an actual path element
|
||||||
|
let prev_is_normal = |p: &Path| -> bool {
|
||||||
|
p.components()
|
||||||
|
.next_back()
|
||||||
|
.map(|c| std::matches!(c, Component::Normal(_)))
|
||||||
|
.unwrap_or(false)
|
||||||
|
};
|
||||||
|
path.as_ref()
|
||||||
|
.components()
|
||||||
|
.for_each(|component| match component {
|
||||||
|
Component::ParentDir if prev_is_normal(&result) => {
|
||||||
|
result.pop();
|
||||||
|
}
|
||||||
|
Component::CurDir if prev_is_normal(&result) => {}
|
||||||
|
_ => result.push(component),
|
||||||
|
});
|
||||||
|
|
||||||
|
Cow::Owned(dunce::simplified(&result).to_path_buf())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expands ~ to home and shortens paths by removing unecessary ".." and "."
|
||||||
|
// where possible. Also expands "...+" appropriately.
|
||||||
|
pub fn expand_path(path: Cow<'_, Path>) -> Cow<'_, Path> {
|
||||||
|
let path = expand_tilde(path);
|
||||||
|
let path = expand_ndots(path);
|
||||||
|
resolve_dots(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn expand_path_string(path: Cow<'_, str>) -> Cow<'_, str> {
|
||||||
|
cow_map_str_path(path, expand_path)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use std::io;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn absolutize_two_dots() {
|
||||||
|
let relative_to = Path::new("/foo/bar");
|
||||||
|
let path = Path::new("..");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
PathBuf::from("/foo"), // missing path
|
||||||
|
absolutize(relative_to, path)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn absolutize_with_curdir() {
|
||||||
|
let relative_to = Path::new("/foo");
|
||||||
|
let path = Path::new("./bar/./baz");
|
||||||
|
|
||||||
|
assert!(!absolutize(relative_to, path)
|
||||||
|
.to_str()
|
||||||
|
.unwrap()
|
||||||
|
.contains('.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn canonicalize_should_succeed() -> io::Result<()> {
|
||||||
|
let relative_to = Path::new("/foo/bar");
|
||||||
|
let path = Path::new("../..");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
PathBuf::from("/"), // existing path
|
||||||
|
canonicalize(relative_to, path)?,
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn canonicalize_should_fail() {
|
||||||
|
let relative_to = Path::new("/foo/bar/baz"); // '/foo' is missing
|
||||||
|
let path = Path::new("../..");
|
||||||
|
|
||||||
|
assert!(canonicalize(relative_to, path).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_ndots_expansion(expected: &str, s: &str) {
|
||||||
|
let expanded = expand_ndots(Cow::Borrowed(Path::new(s)));
|
||||||
|
// If we don't expect expansion, verify that we get a borrow back and no PathBuf creation has been made
|
||||||
|
if expected == s {
|
||||||
|
assert!(
|
||||||
|
std::matches!(expanded, Cow::Borrowed(_)),
|
||||||
|
"No PathBuf should be needed here (unnecessary allocation)"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
assert_eq!(Path::new(expected), &expanded);
|
||||||
|
}
|
||||||
|
|
||||||
|
// common tests
|
||||||
|
#[test]
|
||||||
|
fn string_without_ndots() {
|
||||||
|
check_ndots_expansion("../hola", "../hola");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn string_with_three_ndots_and_chars() {
|
||||||
|
check_ndots_expansion("a...b", "a...b");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn string_with_two_ndots_and_chars() {
|
||||||
|
check_ndots_expansion("a..b", "a..b");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn string_with_one_dot_and_chars() {
|
||||||
|
check_ndots_expansion("a.b", "a.b");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn resolve_dots_double_dots_no_change() {
|
||||||
|
// Can't resolve this as we don't know our parent dir
|
||||||
|
assert_eq!(Path::new(".."), resolve_dots(Path::new("..").into()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn resolve_dots_single_dot_no_change() {
|
||||||
|
// Can't resolve this as we don't know our current dir
|
||||||
|
assert_eq!(Path::new("."), resolve_dots(Path::new(".").into()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn resolve_dots_multi_single_dots_no_change() {
|
||||||
|
assert_eq!(Path::new("././."), resolve_dots(Path::new("././.").into()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn resolve_multi_double_dots_no_change() {
|
||||||
|
assert_eq!(
|
||||||
|
Path::new("../../../"),
|
||||||
|
resolve_dots(Path::new("../../../").into())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn resolve_dots_no_change_with_dirs() {
|
||||||
|
// Can't resolve this as we don't know our parent dir
|
||||||
|
assert_eq!(
|
||||||
|
Path::new("../../../dir1/dir2/"),
|
||||||
|
resolve_dots(Path::new("../../../dir1/dir2").into())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn resolve_dots_simple() {
|
||||||
|
assert_eq!(
|
||||||
|
Path::new("/foo"),
|
||||||
|
resolve_dots(Path::new("/foo/bar/..").into())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn resolve_dots_complex() {
|
||||||
|
assert_eq!(
|
||||||
|
Path::new("/test"),
|
||||||
|
resolve_dots(Path::new("/foo/./bar/../../test/././test2/../").into())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Windows tests
|
||||||
|
#[cfg(windows)]
|
||||||
|
mod windows {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn string_with_three_ndots() {
|
||||||
|
check_ndots_expansion(r"..\..", "...");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn string_with_mixed_ndots_and_chars() {
|
||||||
|
check_ndots_expansion(
|
||||||
|
r"a...b/./c..d/../e.f/..\..\..//.",
|
||||||
|
"a...b/./c..d/../e.f/....//.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn string_with_three_ndots_and_final_slash() {
|
||||||
|
check_ndots_expansion(r"..\../", ".../");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn string_with_three_ndots_and_garbage() {
|
||||||
|
check_ndots_expansion(r"ls ..\../ garbage.*[", "ls .../ garbage.*[");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// non-Windows tests
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
mod non_windows {
|
||||||
|
use super::*;
|
||||||
|
#[test]
|
||||||
|
fn string_with_three_ndots() {
|
||||||
|
check_ndots_expansion(r"../..", "...");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn string_with_mixed_ndots_and_chars() {
|
||||||
|
check_ndots_expansion(
|
||||||
|
"a...b/./c..d/../e.f/../../..//.",
|
||||||
|
"a...b/./c..d/../e.f/....//.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn string_with_three_ndots_and_final_slash() {
|
||||||
|
check_ndots_expansion("../../", ".../");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn string_with_three_ndots_and_garbage() {
|
||||||
|
check_ndots_expansion("ls ../../ garbage.*[", "ls .../ garbage.*[");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod tilde {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn check_expanded(s: &str) {
|
||||||
|
let home = Path::new("/home");
|
||||||
|
let buf = Some(PathBuf::from(home));
|
||||||
|
assert!(expand_tilde_with(Cow::Borrowed(Path::new(s)), buf).starts_with(&home));
|
||||||
|
|
||||||
|
// Tests the special case in expand_tilde for "/" as home
|
||||||
|
let home = Path::new("/");
|
||||||
|
let buf = Some(PathBuf::from(home));
|
||||||
|
assert!(!expand_tilde_with(Cow::Borrowed(Path::new(s)), buf).starts_with("//"));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_not_expanded(s: &str) {
|
||||||
|
let home = PathBuf::from("/home");
|
||||||
|
let expanded = expand_tilde_with(Cow::Borrowed(Path::new(s)), Some(home));
|
||||||
|
assert!(
|
||||||
|
std::matches!(expanded, Cow::Borrowed(_)),
|
||||||
|
"No PathBuf should be needed here (unecessary allocation)"
|
||||||
|
);
|
||||||
|
assert!(&expanded == Path::new(s));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn string_with_tilde() {
|
||||||
|
check_expanded("~");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn string_with_tilde_forward_slash() {
|
||||||
|
check_expanded("~/test/");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn string_with_tilde_double_forward_slash() {
|
||||||
|
check_expanded("~//test/");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn does_not_expand_tilde_if_tilde_is_not_first_character() {
|
||||||
|
check_not_expanded("1~1");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
#[test]
|
||||||
|
fn string_with_tilde_backslash() {
|
||||||
|
check_expanded("~\\test/test2/test3");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
#[test]
|
||||||
|
fn string_with_double_tilde_backslash() {
|
||||||
|
check_expanded("~\\\\test\\test2/test3");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user