Move path handling to nu-path (#3653)

* fixes #3616
This commit is contained in:
Niklas Jonsson
2021-06-20 01:07:26 +02:00
committed by GitHub
parent b9f1371994
commit a8f6a13239
24 changed files with 645 additions and 465 deletions

View File

@ -7,7 +7,6 @@ mod errors;
mod flag;
mod lex;
mod parse;
mod path;
mod scope;
mod shapes;
mod signature;
@ -15,8 +14,6 @@ mod signature;
pub use lex::lexer::{lex, parse_block};
pub use lex::tokens::{LiteBlock, LiteCommand, LiteGroup, LitePipeline};
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 shapes::shapes;
pub use signature::{Signature, SignatureRegistry};

View File

@ -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 indexmap::IndexMap;
use log::trace;
use nu_errors::{ArgumentError, ParseError};
use nu_path::{expand_path, expand_path_string};
use nu_protocol::hir::{
self, Binary, Block, ClassifiedCommand, Expression, ExternalRedirection, Flag, FlagKind, Group,
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 num_bigint::BigInt;
use crate::parse::def::parse_parameter;
use crate::{
lex::lexer::{lex, parse_block},
ParserScope,
@ -24,7 +30,6 @@ use crate::{
},
parse::def::lex_split_baseline_tokens_on,
};
use crate::{parse::def::parse_parameter, path::expand_path};
use self::{
def::{parse_definition, parse_definition_prototype},
@ -67,7 +72,7 @@ pub fn parse_simple_column_path(
output.push(Member::Int(row_number, part_span));
} else {
let trimmed = trim_quotes(&current_part);
output.push(Member::Bare(trimmed.clone().spanned(part_span)));
output.push(Member::Bare(trimmed.spanned(part_span)));
}
current_part.clear();
// Note: I believe this is safe because of the delimiter we're using,
@ -944,8 +949,8 @@ fn parse_arg(
)
}
SyntaxShape::GlobPattern => {
let trimmed = trim_quotes(&lite_arg.item);
let expanded = expand_path(&trimmed).to_string();
let trimmed = Cow::Owned(trim_quotes(&lite_arg.item));
let expanded = expand_path_string(trimmed).to_string();
(
SpannedExpression::new(Expression::glob_pattern(expanded), lite_arg.span),
None,
@ -961,10 +966,10 @@ fn parse_arg(
SyntaxShape::Duration => parse_duration(lite_arg),
SyntaxShape::FilePath => {
let trimmed = trim_quotes(&lite_arg.item);
let expanded = expand_path(&trimmed).to_string();
let path = Path::new(&expanded);
let path = PathBuf::from(trimmed);
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,
)
}
@ -1647,8 +1652,8 @@ fn parse_external_call(
) -> (Option<ClassifiedCommand>, Option<ParseError>) {
let mut error = None;
let name = lite_cmd.parts[0].clone().map(|v| {
let trimmed = trim_quotes(&v);
expand_path(&trimmed).to_string()
let trimmed = Cow::Owned(trim_quotes(&v));
expand_path_string(trimmed).to_string()
});
let mut args = vec![];
@ -1877,9 +1882,9 @@ fn parse_call(
)),
);
}
if let Ok(contents) =
std::fs::read_to_string(expand_path(&lite_cmd.parts[1].item).into_owned())
{
if let Ok(contents) = std::fs::read_to_string(&expand_path(Cow::Borrowed(Path::new(
&lite_cmd.parts[1].item,
)))) {
let _ = parse(&contents, 0, scope);
} else {
return (

View File

@ -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(),
);
}
}