forked from extern/nushell
Path Command Enhancement Project (#2742)
* Add string argument support for path subcommands * Add --replace option to 'path extension' command * Add examples of replacing for path extension * Refactor path extension and its example * Add replacement functionality to path basename * Refactor path subcommands to support more args This adds a lot of redundancy to non-relevant subcommands such as type, exists or expand. * Add replace and num_levels options to path dirname * Rename num_levels option to num-levels * Remove commented code * Clean up path basename * Fix path dirname description * Add path filestem opts; Rename extension -> suffix * Add prefix option and examples to path filestem * Fix broken num-levels of path dirname * Fix failing example test of path filestem * Fix failing test of path extension * Formatting * Add Windows-specific path subcommand examples `path expand` is still broken but otherwise seems to fix all examples on Windows * Fix weird path expand on Windows Also disable example tests for path expand. Failed caconicalization (e.g., due to path not existing) returns the original path so the examples always fail. * Formatting * Return path datatype when appropriate * Do not append empty remainder to path dirname * Add tests for path subcommands * Formatting * Revisit path subcommand description strings * Apply clippy suggestions; Formatting * Remove problematic test checking '~' expansion Wouldn't run on minimal due to useing optional dependency. The test success was also deending on the presence of home dir on the testing machine which might not be completely robust. * Add missing newline to file
This commit is contained in:
parent
930f9f0063
commit
8b597187fc
@ -2,11 +2,18 @@ use super::{operate, DefaultArguments};
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_protocol::{ColumnPath, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::Tagged;
|
||||
use std::path::Path;
|
||||
|
||||
pub struct PathBasename;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct PathBasenameArguments {
|
||||
replace: Option<Tagged<String>>,
|
||||
rest: Vec<ColumnPath>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for PathBasename {
|
||||
fn name(&self) -> &str {
|
||||
@ -15,11 +22,17 @@ impl WholeStreamCommand for PathBasename {
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("path basename")
|
||||
.rest(SyntaxShape::ColumnPath, "optionally operate by path")
|
||||
.named(
|
||||
"replace",
|
||||
SyntaxShape::String,
|
||||
"Return original path with basename replaced by this string",
|
||||
Some('r'),
|
||||
)
|
||||
.rest(SyntaxShape::ColumnPath, "Optionally operate by column path")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"gets the filename of a path"
|
||||
"Gets the final component of a path"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
@ -28,24 +41,60 @@ impl WholeStreamCommand for PathBasename {
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let (DefaultArguments { rest }, input) = args.process(®istry).await?;
|
||||
operate(input, rest, &action, tag.span).await
|
||||
let (PathBasenameArguments { replace, rest }, input) = args.process(®istry).await?;
|
||||
let args = Arc::new(DefaultArguments {
|
||||
replace: replace.map(|v| v.item),
|
||||
prefix: None,
|
||||
suffix: None,
|
||||
num_levels: None,
|
||||
paths: rest,
|
||||
});
|
||||
operate(input, &action, tag.span, args).await
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Get basename of a path",
|
||||
example: "echo '/home/joe/test.txt' | path basename",
|
||||
result: Some(vec![Value::from("test.txt")]),
|
||||
}]
|
||||
vec![
|
||||
Example {
|
||||
description: "Get basename of a path",
|
||||
example: "echo 'C:\\Users\\joe\\test.txt' | path basename",
|
||||
result: Some(vec![Value::from("test.txt")]),
|
||||
},
|
||||
Example {
|
||||
description: "Replace basename of a path",
|
||||
example: "echo 'C:\\Users\\joe\\test.txt' | path basename -r 'spam.png'",
|
||||
result: Some(vec![Value::from(UntaggedValue::path(
|
||||
"C:\\Users\\joe\\spam.png",
|
||||
))]),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Get basename of a path",
|
||||
example: "echo '/home/joe/test.txt' | path basename",
|
||||
result: Some(vec![Value::from("test.txt")]),
|
||||
},
|
||||
Example {
|
||||
description: "Replace basename of a path",
|
||||
example: "echo '/home/joe/test.txt' | path basename -r 'spam.png'",
|
||||
result: Some(vec![Value::from(UntaggedValue::path("/home/joe/spam.png"))]),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn action(path: &Path) -> UntaggedValue {
|
||||
UntaggedValue::string(match path.file_name() {
|
||||
Some(filename) => filename.to_string_lossy().to_string(),
|
||||
_ => "".to_string(),
|
||||
})
|
||||
fn action(path: &Path, args: Arc<DefaultArguments>) -> UntaggedValue {
|
||||
match args.replace {
|
||||
Some(ref basename) => UntaggedValue::path(path.with_file_name(basename)),
|
||||
None => UntaggedValue::string(match path.file_name() {
|
||||
Some(filename) => filename.to_string_lossy(),
|
||||
None => "".into(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -16,7 +16,7 @@ impl WholeStreamCommand for Path {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Apply path function"
|
||||
"Explore and manipulate paths"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
|
@ -2,11 +2,20 @@ use super::{operate, DefaultArguments};
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_protocol::{ColumnPath, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::Tagged;
|
||||
use std::path::Path;
|
||||
|
||||
pub struct PathDirname;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct PathDirnameArguments {
|
||||
replace: Option<Tagged<String>>,
|
||||
#[serde(rename = "num-levels")]
|
||||
num_levels: Option<Tagged<u32>>,
|
||||
rest: Vec<ColumnPath>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for PathDirname {
|
||||
fn name(&self) -> &str {
|
||||
@ -14,11 +23,24 @@ impl WholeStreamCommand for PathDirname {
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("path dirname").rest(SyntaxShape::ColumnPath, "optionally operate by path")
|
||||
Signature::build("path dirname")
|
||||
.named(
|
||||
"replace",
|
||||
SyntaxShape::String,
|
||||
"Return original path with dirname replaced by this string",
|
||||
Some('r'),
|
||||
)
|
||||
.named(
|
||||
"num-levels",
|
||||
SyntaxShape::Int,
|
||||
"Number of directories to walk up",
|
||||
Some('n'),
|
||||
)
|
||||
.rest(SyntaxShape::ColumnPath, "Optionally operate by column path")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"gets the dirname of a path"
|
||||
"Gets the parent directory of a path"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
@ -27,24 +49,100 @@ impl WholeStreamCommand for PathDirname {
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let (DefaultArguments { rest }, input) = args.process(®istry).await?;
|
||||
operate(input, rest, &action, tag.span).await
|
||||
let (
|
||||
PathDirnameArguments {
|
||||
replace,
|
||||
num_levels,
|
||||
rest,
|
||||
},
|
||||
input,
|
||||
) = args.process(®istry).await?;
|
||||
let args = Arc::new(DefaultArguments {
|
||||
replace: replace.map(|v| v.item),
|
||||
prefix: None,
|
||||
suffix: None,
|
||||
num_levels: num_levels.map(|v| v.item),
|
||||
paths: rest,
|
||||
});
|
||||
operate(input, &action, tag.span, args).await
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Get dirname of a path",
|
||||
example: "echo '/home/joe/test.txt' | path dirname",
|
||||
result: Some(vec![Value::from("/home/joe")]),
|
||||
}]
|
||||
vec![
|
||||
Example {
|
||||
description: "Get dirname of a path",
|
||||
example: "echo 'C:\\Users\\joe\\code\\test.txt' | path dirname",
|
||||
result: Some(vec![Value::from(UntaggedValue::path(
|
||||
"C:\\Users\\joe\\code",
|
||||
))]),
|
||||
},
|
||||
Example {
|
||||
description: "Set how many levels up to skip",
|
||||
example: "echo 'C:\\Users\\joe\\code\\test.txt' | path dirname -n 2",
|
||||
result: Some(vec![Value::from(UntaggedValue::path("C:\\Users\\joe"))]),
|
||||
},
|
||||
Example {
|
||||
description: "Replace the part that would be returned with custom string",
|
||||
example:
|
||||
"echo 'C:\\Users\\joe\\code\\test.txt' | path dirname -n 2 -r C:\\Users\\viking",
|
||||
result: Some(vec![Value::from(UntaggedValue::path(
|
||||
"C:\\Users\\viking\\code\\test.txt",
|
||||
))]),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Get dirname of a path",
|
||||
example: "echo '/home/joe/code/test.txt' | path dirname",
|
||||
result: Some(vec![Value::from(UntaggedValue::path("/home/joe/code"))]),
|
||||
},
|
||||
Example {
|
||||
description: "Set how many levels up to skip",
|
||||
example: "echo '/home/joe/code/test.txt' | path dirname -n 2",
|
||||
result: Some(vec![Value::from(UntaggedValue::path("/home/joe"))]),
|
||||
},
|
||||
Example {
|
||||
description: "Replace the part that would be returned with custom string",
|
||||
example: "echo '/home/joe/code/test.txt' | path dirname -n 2 -r /home/viking",
|
||||
result: Some(vec![Value::from(UntaggedValue::path(
|
||||
"/home/viking/code/test.txt",
|
||||
))]),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn action(path: &Path) -> UntaggedValue {
|
||||
UntaggedValue::string(match path.parent() {
|
||||
Some(dirname) => dirname.to_string_lossy().to_string(),
|
||||
_ => "".to_string(),
|
||||
})
|
||||
fn action(path: &Path, args: Arc<DefaultArguments>) -> UntaggedValue {
|
||||
let num_levels = args.num_levels.unwrap_or(1);
|
||||
|
||||
let mut dirname = path;
|
||||
let mut reached_top = false; // end early if somebody passes -n 99999999
|
||||
for _ in 0..num_levels {
|
||||
dirname = dirname.parent().unwrap_or_else(|| {
|
||||
reached_top = true;
|
||||
dirname
|
||||
});
|
||||
if reached_top {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
match args.replace {
|
||||
Some(ref newdir) => {
|
||||
let remainder = path.strip_prefix(dirname).unwrap_or(dirname);
|
||||
if !remainder.as_os_str().is_empty() {
|
||||
UntaggedValue::path(Path::new(newdir).join(remainder))
|
||||
} else {
|
||||
UntaggedValue::path(Path::new(newdir))
|
||||
}
|
||||
}
|
||||
None => UntaggedValue::path(dirname),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -2,11 +2,16 @@ use super::{operate, DefaultArguments};
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_protocol::{ColumnPath, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use std::path::Path;
|
||||
|
||||
pub struct PathExists;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct PathExistsArguments {
|
||||
rest: Vec<ColumnPath>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for PathExists {
|
||||
fn name(&self) -> &str {
|
||||
@ -14,11 +19,12 @@ impl WholeStreamCommand for PathExists {
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("path exists").rest(SyntaxShape::ColumnPath, "optionally operate by path")
|
||||
Signature::build("path exists")
|
||||
.rest(SyntaxShape::ColumnPath, "Optionally operate by column path")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"checks whether the path exists"
|
||||
"Checks whether a path exists"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
@ -27,10 +33,27 @@ impl WholeStreamCommand for PathExists {
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let (DefaultArguments { rest }, input) = args.process(®istry).await?;
|
||||
operate(input, rest, &action, tag.span).await
|
||||
let (PathExistsArguments { rest }, input) = args.process(®istry).await?;
|
||||
let args = Arc::new(DefaultArguments {
|
||||
replace: None,
|
||||
prefix: None,
|
||||
suffix: None,
|
||||
num_levels: None,
|
||||
paths: rest,
|
||||
});
|
||||
operate(input, &action, tag.span, args).await
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Check if file exists",
|
||||
example: "echo 'C:\\Users\\joe\\todo.txt' | path exists",
|
||||
result: Some(vec![Value::from(UntaggedValue::boolean(false))]),
|
||||
}]
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Check if file exists",
|
||||
@ -40,7 +63,7 @@ impl WholeStreamCommand for PathExists {
|
||||
}
|
||||
}
|
||||
|
||||
fn action(path: &Path) -> UntaggedValue {
|
||||
fn action(path: &Path, _args: Arc<DefaultArguments>) -> UntaggedValue {
|
||||
UntaggedValue::boolean(path.exists())
|
||||
}
|
||||
|
||||
|
@ -2,11 +2,16 @@ use super::{operate, DefaultArguments};
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue};
|
||||
use std::path::Path;
|
||||
use nu_protocol::{ColumnPath, Signature, SyntaxShape, UntaggedValue};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
pub struct PathExpand;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct PathExpandArguments {
|
||||
rest: Vec<ColumnPath>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for PathExpand {
|
||||
fn name(&self) -> &str {
|
||||
@ -14,11 +19,12 @@ impl WholeStreamCommand for PathExpand {
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("path expand").rest(SyntaxShape::ColumnPath, "optionally operate by path")
|
||||
Signature::build("path expand")
|
||||
.rest(SyntaxShape::ColumnPath, "Optionally operate by column path")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"expands the path to its absolute form"
|
||||
"Expands a path to its absolute form"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
@ -27,28 +33,43 @@ impl WholeStreamCommand for PathExpand {
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let (DefaultArguments { rest }, input) = args.process(®istry).await?;
|
||||
operate(input, rest, &action, tag.span).await
|
||||
let (PathExpandArguments { rest }, input) = args.process(®istry).await?;
|
||||
let args = Arc::new(DefaultArguments {
|
||||
replace: None,
|
||||
prefix: None,
|
||||
suffix: None,
|
||||
num_levels: None,
|
||||
paths: rest,
|
||||
});
|
||||
operate(input, &action, tag.span, args).await
|
||||
}
|
||||
|
||||
#[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
|
||||
}]
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Expand relative directories",
|
||||
example: "echo '/home/joe/foo/../bar' | path expand",
|
||||
result: None,
|
||||
//Some(vec![Value::from("/home/joe/bar")]),
|
||||
// fails to canonicalize into Some(vec![Value::from("/home/joe/bar")]) due to non-existing path
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
fn action(path: &Path) -> UntaggedValue {
|
||||
fn action(path: &Path, _args: Arc<DefaultArguments>) -> UntaggedValue {
|
||||
let ps = path.to_string_lossy();
|
||||
let expanded = shellexpand::tilde(&ps);
|
||||
let path: &Path = expanded.as_ref().as_ref();
|
||||
UntaggedValue::string(match path.canonicalize() {
|
||||
Ok(p) => p.to_string_lossy().to_string(),
|
||||
Err(_) => ps.to_string(),
|
||||
})
|
||||
UntaggedValue::path(dunce::canonicalize(path).unwrap_or_else(|_| PathBuf::from(path)))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -2,11 +2,18 @@ use super::{operate, DefaultArguments};
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_protocol::{ColumnPath, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::Tagged;
|
||||
use std::path::Path;
|
||||
|
||||
pub struct PathExtension;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct PathExtensionArguments {
|
||||
replace: Option<Tagged<String>>,
|
||||
rest: Vec<ColumnPath>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for PathExtension {
|
||||
fn name(&self) -> &str {
|
||||
@ -15,11 +22,17 @@ impl WholeStreamCommand for PathExtension {
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("path extension")
|
||||
.rest(SyntaxShape::ColumnPath, "optionally operate by path")
|
||||
.named(
|
||||
"replace",
|
||||
SyntaxShape::String,
|
||||
"Return original path with extension replaced by this string",
|
||||
Some('r'),
|
||||
)
|
||||
.rest(SyntaxShape::ColumnPath, "Optionally operate by column path")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"gets the extension of a path"
|
||||
"Gets the extension of a path"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
@ -28,8 +41,15 @@ impl WholeStreamCommand for PathExtension {
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let (DefaultArguments { rest }, input) = args.process(®istry).await?;
|
||||
operate(input, rest, &action, tag.span).await
|
||||
let (PathExtensionArguments { replace, rest }, input) = args.process(®istry).await?;
|
||||
let args = Arc::new(DefaultArguments {
|
||||
replace: replace.map(|v| v.item),
|
||||
prefix: None,
|
||||
suffix: None,
|
||||
num_levels: None,
|
||||
paths: rest,
|
||||
});
|
||||
operate(input, &action, tag.span, args).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -44,15 +64,28 @@ impl WholeStreamCommand for PathExtension {
|
||||
example: "echo 'test' | path extension",
|
||||
result: Some(vec![Value::from("")]),
|
||||
},
|
||||
Example {
|
||||
description: "Replace an extension with a custom string",
|
||||
example: "echo 'test.txt' | path extension -r md",
|
||||
result: Some(vec![Value::from(UntaggedValue::path("test.md"))]),
|
||||
},
|
||||
Example {
|
||||
description: "To replace more complex extensions:",
|
||||
example: "echo 'test.tar.gz' | path extension -r '' | path extension -r txt",
|
||||
result: Some(vec![Value::from(UntaggedValue::path("test.txt"))]),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn action(path: &Path) -> UntaggedValue {
|
||||
UntaggedValue::string(match path.extension() {
|
||||
Some(ext) => ext.to_string_lossy().to_string(),
|
||||
_ => "".to_string(),
|
||||
})
|
||||
fn action(path: &Path, args: Arc<DefaultArguments>) -> UntaggedValue {
|
||||
match args.replace {
|
||||
Some(ref extension) => UntaggedValue::path(path.with_extension(extension)),
|
||||
None => UntaggedValue::string(match path.extension() {
|
||||
Some(extension) => extension.to_string_lossy(),
|
||||
None => "".into(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -2,11 +2,20 @@ use super::{operate, DefaultArguments};
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_protocol::{ColumnPath, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::Tagged;
|
||||
use std::path::Path;
|
||||
|
||||
pub struct PathFilestem;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct PathFilestemArguments {
|
||||
prefix: Option<Tagged<String>>,
|
||||
suffix: Option<Tagged<String>>,
|
||||
replace: Option<Tagged<String>>,
|
||||
rest: Vec<ColumnPath>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for PathFilestem {
|
||||
fn name(&self) -> &str {
|
||||
@ -15,11 +24,29 @@ impl WholeStreamCommand for PathFilestem {
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("path filestem")
|
||||
.rest(SyntaxShape::ColumnPath, "optionally operate by path")
|
||||
.named(
|
||||
"replace",
|
||||
SyntaxShape::String,
|
||||
"Return original path with filestem replaced by this string",
|
||||
Some('r'),
|
||||
)
|
||||
.named(
|
||||
"prefix",
|
||||
SyntaxShape::String,
|
||||
"Strip this string from from the beginning of a file name",
|
||||
Some('p'),
|
||||
)
|
||||
.named(
|
||||
"suffix",
|
||||
SyntaxShape::String,
|
||||
"Strip this string from from the end of a file name",
|
||||
Some('s'),
|
||||
)
|
||||
.rest(SyntaxShape::ColumnPath, "Optionally operate by column path")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"gets the filestem of a path"
|
||||
"Gets the file stem of a path"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
@ -28,24 +55,111 @@ impl WholeStreamCommand for PathFilestem {
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let (DefaultArguments { rest }, input) = args.process(®istry).await?;
|
||||
operate(input, rest, &action, tag.span).await
|
||||
let (
|
||||
PathFilestemArguments {
|
||||
replace,
|
||||
prefix,
|
||||
suffix,
|
||||
rest,
|
||||
},
|
||||
input,
|
||||
) = args.process(®istry).await?;
|
||||
let args = Arc::new(DefaultArguments {
|
||||
replace: replace.map(|v| v.item),
|
||||
prefix: prefix.map(|v| v.item),
|
||||
suffix: suffix.map(|v| v.item),
|
||||
num_levels: None,
|
||||
paths: rest,
|
||||
});
|
||||
operate(input, &action, tag.span, args).await
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Get filestem of a path",
|
||||
example: "echo '/home/joe/test.txt' | path filestem",
|
||||
result: Some(vec![Value::from("test")]),
|
||||
}]
|
||||
vec![
|
||||
Example {
|
||||
description: "Get filestem of a path",
|
||||
example: "echo 'C:\\Users\\joe\\bacon_lettuce.egg' | path filestem",
|
||||
result: Some(vec![Value::from("bacon_lettuce")]),
|
||||
},
|
||||
Example {
|
||||
description: "Get filestem of a path, stripped of prefix and suffix",
|
||||
example: "echo 'C:\\Users\\joe\\bacon_lettuce.egg.gz' | path filestem -p bacon_ -s .egg.gz",
|
||||
result: Some(vec![Value::from("lettuce")]),
|
||||
},
|
||||
Example {
|
||||
description: "Replace the filestem that would be returned",
|
||||
example: "echo 'C:\\Users\\joe\\bacon_lettuce.egg.gz' | path filestem -p bacon_ -s .egg.gz -r spam",
|
||||
result: Some(vec![Value::from(UntaggedValue::path("C:\\Users\\joe\\bacon_spam.egg.gz"))]),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Get filestem of a path",
|
||||
example: "echo '/home/joe/bacon_lettuce.egg' | path filestem",
|
||||
result: Some(vec![Value::from("bacon_lettuce")]),
|
||||
},
|
||||
Example {
|
||||
description: "Get filestem of a path, stripped of prefix and suffix",
|
||||
example: "echo '/home/joe/bacon_lettuce.egg.gz' | path filestem -p bacon_ -s .egg.gz",
|
||||
result: Some(vec![Value::from("lettuce")]),
|
||||
},
|
||||
Example {
|
||||
description: "Replace the filestem that would be returned",
|
||||
example: "echo '/home/joe/bacon_lettuce.egg.gz' | path filestem -p bacon_ -s .egg.gz -r spam",
|
||||
result: Some(vec![Value::from(UntaggedValue::path("/home/joe/bacon_spam.egg.gz"))]),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn action(path: &Path) -> UntaggedValue {
|
||||
UntaggedValue::string(match path.file_stem() {
|
||||
Some(stem) => stem.to_string_lossy().to_string(),
|
||||
_ => "".to_string(),
|
||||
})
|
||||
fn action(path: &Path, args: Arc<DefaultArguments>) -> UntaggedValue {
|
||||
let basename = match path.file_name() {
|
||||
Some(name) => name.to_string_lossy().to_string(),
|
||||
None => "".to_string(),
|
||||
};
|
||||
|
||||
let suffix = match args.suffix {
|
||||
Some(ref suf) => match basename.rmatch_indices(suf).next() {
|
||||
Some((i, _)) => basename.split_at(i).1.to_string(),
|
||||
None => "".to_string(),
|
||||
},
|
||||
None => match path.extension() {
|
||||
// Prepend '.' since the extension returned comes without it
|
||||
Some(ext) => ".".to_string() + &ext.to_string_lossy().to_string(),
|
||||
None => "".to_string(),
|
||||
},
|
||||
};
|
||||
|
||||
let prefix = match args.prefix {
|
||||
Some(ref pre) => match basename.matches(pre).next() {
|
||||
Some(m) => basename.split_at(m.len()).0.to_string(),
|
||||
None => "".to_string(),
|
||||
},
|
||||
None => "".to_string(),
|
||||
};
|
||||
|
||||
let basename_without_prefix = match basename.matches(&prefix).next() {
|
||||
Some(m) => basename.split_at(m.len()).1.to_string(),
|
||||
None => basename,
|
||||
};
|
||||
|
||||
let stem = match basename_without_prefix.rmatch_indices(&suffix).next() {
|
||||
Some((i, _)) => basename_without_prefix.split_at(i).0.to_string(),
|
||||
None => basename_without_prefix,
|
||||
};
|
||||
|
||||
match args.replace {
|
||||
Some(ref replace) => {
|
||||
let new_name = prefix + replace + &suffix;
|
||||
UntaggedValue::path(path.with_file_name(&new_name))
|
||||
}
|
||||
None => UntaggedValue::string(stem),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -12,6 +12,7 @@ use nu_errors::ShellError;
|
||||
use nu_protocol::{ColumnPath, Primitive, ReturnSuccess, ShellTypeName, UntaggedValue, Value};
|
||||
use nu_source::Span;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub use basename::PathBasename;
|
||||
pub use command::Path as PathCommand;
|
||||
@ -24,17 +25,32 @@ pub use r#type::PathType;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct DefaultArguments {
|
||||
rest: Vec<ColumnPath>,
|
||||
// used by basename, dirname, extension and filestem
|
||||
replace: Option<String>,
|
||||
// used by filestem
|
||||
prefix: Option<String>,
|
||||
suffix: Option<String>,
|
||||
// used by dirname
|
||||
num_levels: Option<u32>,
|
||||
// used by all
|
||||
paths: Vec<ColumnPath>,
|
||||
}
|
||||
|
||||
fn handle_value<F>(action: &F, v: &Value, span: Span) -> Result<Value, ShellError>
|
||||
fn handle_value<F>(
|
||||
action: &F,
|
||||
v: &Value,
|
||||
span: Span,
|
||||
args: Arc<DefaultArguments>,
|
||||
) -> Result<Value, ShellError>
|
||||
where
|
||||
F: Fn(&Path) -> UntaggedValue + Send + 'static,
|
||||
F: Fn(&Path, Arc<DefaultArguments>) -> UntaggedValue + Send + 'static,
|
||||
{
|
||||
let v = match &v.value {
|
||||
UntaggedValue::Primitive(Primitive::Path(buf)) => action(buf).into_value(v.tag()),
|
||||
UntaggedValue::Primitive(Primitive::Path(buf)) => action(buf, args).into_value(v.tag()),
|
||||
UntaggedValue::Primitive(Primitive::String(s))
|
||||
| UntaggedValue::Primitive(Primitive::Line(s)) => action(s.as_ref()).into_value(v.tag()),
|
||||
| UntaggedValue::Primitive(Primitive::Line(s)) => {
|
||||
action(s.as_ref(), args).into_value(v.tag())
|
||||
}
|
||||
other => {
|
||||
let got = format!("got {}", other.type_name());
|
||||
return Err(ShellError::labeled_error_with_secondary(
|
||||
@ -51,24 +67,25 @@ where
|
||||
|
||||
async fn operate<F>(
|
||||
input: crate::InputStream,
|
||||
paths: Vec<ColumnPath>,
|
||||
action: &'static F,
|
||||
span: Span,
|
||||
args: Arc<DefaultArguments>,
|
||||
) -> Result<OutputStream, ShellError>
|
||||
where
|
||||
F: Fn(&Path) -> UntaggedValue + Send + Sync + 'static,
|
||||
F: Fn(&Path, Arc<DefaultArguments>) -> UntaggedValue + Send + Sync + 'static,
|
||||
{
|
||||
Ok(input
|
||||
.map(move |v| {
|
||||
if paths.is_empty() {
|
||||
ReturnSuccess::value(handle_value(&action, &v, span)?)
|
||||
if args.paths.is_empty() {
|
||||
ReturnSuccess::value(handle_value(&action, &v, span, Arc::clone(&args))?)
|
||||
} else {
|
||||
let mut ret = v;
|
||||
|
||||
for path in &paths {
|
||||
for path in &args.paths {
|
||||
let cloned_args = Arc::clone(&args);
|
||||
ret = ret.swap_data_by_column_path(
|
||||
path,
|
||||
Box::new(move |old| handle_value(&action, &old, span)),
|
||||
Box::new(move |old| handle_value(&action, &old, span, cloned_args)),
|
||||
)?;
|
||||
}
|
||||
|
||||
|
@ -3,11 +3,16 @@ use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use crate::shell::filesystem_shell::get_file_type;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_protocol::{ColumnPath, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use std::path::Path;
|
||||
|
||||
pub struct PathType;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct PathTypeArguments {
|
||||
rest: Vec<ColumnPath>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for PathType {
|
||||
fn name(&self) -> &str {
|
||||
@ -15,11 +20,12 @@ impl WholeStreamCommand for PathType {
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("path type").rest(SyntaxShape::ColumnPath, "optionally operate by path")
|
||||
Signature::build("path type")
|
||||
.rest(SyntaxShape::ColumnPath, "Optionally operate by column path")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"gives the type of the object the path refers to (eg file, dir, symlink)"
|
||||
"Gives the type of the object a path refers to (e.g., file, dir, symlink)"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
@ -28,8 +34,15 @@ impl WholeStreamCommand for PathType {
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let tag = args.call_info.name_tag.clone();
|
||||
let (DefaultArguments { rest }, input) = args.process(®istry).await?;
|
||||
operate(input, rest, &action, tag.span).await
|
||||
let (PathTypeArguments { rest }, input) = args.process(®istry).await?;
|
||||
let args = Arc::new(DefaultArguments {
|
||||
replace: None,
|
||||
prefix: None,
|
||||
suffix: None,
|
||||
num_levels: None,
|
||||
paths: rest,
|
||||
});
|
||||
operate(input, &action, tag.span, args).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
@ -41,7 +54,7 @@ impl WholeStreamCommand for PathType {
|
||||
}
|
||||
}
|
||||
|
||||
fn action(path: &Path) -> UntaggedValue {
|
||||
fn action(path: &Path, _args: Arc<DefaultArguments>) -> UntaggedValue {
|
||||
let meta = std::fs::symlink_metadata(path);
|
||||
UntaggedValue::string(match &meta {
|
||||
Ok(md) => get_file_type(md),
|
||||
|
@ -34,6 +34,7 @@ mod mkdir;
|
||||
mod move_;
|
||||
mod open;
|
||||
mod parse;
|
||||
mod path;
|
||||
mod prepend;
|
||||
mod random;
|
||||
mod range;
|
||||
|
83
crates/nu-cli/tests/commands/path/basename.rs
Normal file
83
crates/nu-cli/tests/commands/path/basename.rs
Normal file
@ -0,0 +1,83 @@
|
||||
use nu_test_support::{nu, pipeline};
|
||||
|
||||
use super::join_path_sep;
|
||||
|
||||
#[test]
|
||||
fn returns_basename_of_empty_input() {
|
||||
let actual = nu!(
|
||||
cwd: "tests", pipeline(
|
||||
r#"
|
||||
echo ""
|
||||
| path basename
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn replaces_basename_of_empty_input() {
|
||||
let actual = nu!(
|
||||
cwd: "tests", pipeline(
|
||||
r#"
|
||||
echo ""
|
||||
| path basename -r newname.txt
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "newname.txt");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn returns_basename_of_path_ending_with_dot() {
|
||||
let actual = nu!(
|
||||
cwd: "tests", pipeline(
|
||||
r#"
|
||||
echo "some/file.txt/."
|
||||
| path basename
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "file.txt");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn replaces_basename_of_path_ending_with_dot() {
|
||||
let actual = nu!(
|
||||
cwd: "tests", pipeline(
|
||||
r#"
|
||||
echo "some/file.txt/."
|
||||
| path basename -r viking.txt
|
||||
"#
|
||||
));
|
||||
|
||||
let expected = join_path_sep(&["some", "viking.txt"]);
|
||||
assert_eq!(actual.out, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn returns_basename_of_path_ending_with_double_dot() {
|
||||
let actual = nu!(
|
||||
cwd: "tests", pipeline(
|
||||
r#"
|
||||
echo "some/file.txt/.."
|
||||
| path basename
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn replaces_basename_of_path_ending_with_double_dot() {
|
||||
let actual = nu!(
|
||||
cwd: "tests", pipeline(
|
||||
r#"
|
||||
echo "some/file.txt/.."
|
||||
| path basename -r eggs
|
||||
"#
|
||||
));
|
||||
|
||||
let expected = join_path_sep(&["some/file.txt/..", "eggs"]);
|
||||
assert_eq!(actual.out, expected);
|
||||
}
|
137
crates/nu-cli/tests/commands/path/dirname.rs
Normal file
137
crates/nu-cli/tests/commands/path/dirname.rs
Normal file
@ -0,0 +1,137 @@
|
||||
use nu_test_support::{nu, pipeline};
|
||||
|
||||
use super::join_path_sep;
|
||||
|
||||
#[test]
|
||||
fn returns_dirname_of_empty_input() {
|
||||
let actual = nu!(
|
||||
cwd: "tests", pipeline(
|
||||
r#"
|
||||
echo ""
|
||||
| path dirname
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn replaces_dirname_of_empty_input() {
|
||||
let actual = nu!(
|
||||
cwd: "tests", pipeline(
|
||||
r#"
|
||||
echo ""
|
||||
| path dirname -r newdir
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "newdir");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn returns_dirname_of_path_ending_with_dot() {
|
||||
let actual = nu!(
|
||||
cwd: "tests", pipeline(
|
||||
r#"
|
||||
echo "some/dir/."
|
||||
| path dirname
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "some");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn replaces_dirname_of_path_ending_with_dot() {
|
||||
let actual = nu!(
|
||||
cwd: "tests", pipeline(
|
||||
r#"
|
||||
echo "some/dir/."
|
||||
| path dirname -r eggs
|
||||
"#
|
||||
));
|
||||
|
||||
let expected = join_path_sep(&["eggs", "dir"]);
|
||||
assert_eq!(actual.out, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn returns_dirname_of_path_ending_with_double_dot() {
|
||||
let actual = nu!(
|
||||
cwd: "tests", pipeline(
|
||||
r#"
|
||||
echo "some/dir/.."
|
||||
| path dirname
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "some/dir");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn replaces_dirname_of_path_with_double_dot() {
|
||||
let actual = nu!(
|
||||
cwd: "tests", pipeline(
|
||||
r#"
|
||||
echo "some/dir/.."
|
||||
| path dirname -r eggs
|
||||
"#
|
||||
));
|
||||
|
||||
let expected = join_path_sep(&["eggs", ".."]);
|
||||
assert_eq!(actual.out, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn returns_dirname_of_zero_levels() {
|
||||
let actual = nu!(
|
||||
cwd: "tests", pipeline(
|
||||
r#"
|
||||
echo "some/dir/with/spam.txt"
|
||||
| path dirname -n 0
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "some/dir/with/spam.txt");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn replaces_dirname_of_zero_levels_with_empty_string() {
|
||||
let actual = nu!(
|
||||
cwd: "tests", pipeline(
|
||||
r#"
|
||||
echo "some/dir/with/spam.txt"
|
||||
| path dirname -n 0 -r ""
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn replaces_dirname_of_more_levels() {
|
||||
let actual = nu!(
|
||||
cwd: "tests", pipeline(
|
||||
r#"
|
||||
echo "some/dir/with/spam.txt"
|
||||
| path dirname -r eggs -n 2
|
||||
"#
|
||||
));
|
||||
|
||||
let expected = join_path_sep(&["eggs", "with/spam.txt"]);
|
||||
assert_eq!(actual.out, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn replaces_dirname_of_way_too_many_levels() {
|
||||
let actual = nu!(
|
||||
cwd: "tests", pipeline(
|
||||
r#"
|
||||
echo "some/dir/with/spam.txt"
|
||||
| path dirname -r eggs -n 999
|
||||
"#
|
||||
));
|
||||
|
||||
let expected = join_path_sep(&["eggs", "some/dir/with/spam.txt"]);
|
||||
assert_eq!(actual.out, expected);
|
||||
}
|
53
crates/nu-cli/tests/commands/path/exists.rs
Normal file
53
crates/nu-cli/tests/commands/path/exists.rs
Normal file
@ -0,0 +1,53 @@
|
||||
use nu_test_support::fs::Stub::EmptyFile;
|
||||
use nu_test_support::nu;
|
||||
use nu_test_support::playground::Playground;
|
||||
|
||||
#[test]
|
||||
fn checks_if_existing_file_exists() {
|
||||
Playground::setup("path_exists_1", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![EmptyFile("spam.txt")]);
|
||||
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(),
|
||||
"echo spam.txt | path exists"
|
||||
);
|
||||
|
||||
assert_eq!(actual.out, "true");
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn checks_if_missing_file_exists() {
|
||||
Playground::setup("path_exists_2", |dirs, _| {
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(),
|
||||
"echo spam.txt | path exists"
|
||||
);
|
||||
|
||||
assert_eq!(actual.out, "false");
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn checks_if_dot_exists() {
|
||||
Playground::setup("path_exists_3", |dirs, _| {
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(),
|
||||
"echo '.' | path exists"
|
||||
);
|
||||
|
||||
assert_eq!(actual.out, "true");
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn checks_if_double_dot_exists() {
|
||||
Playground::setup("path_exists_4", |dirs, _| {
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(),
|
||||
"echo '..' | path exists"
|
||||
);
|
||||
|
||||
assert_eq!(actual.out, "true");
|
||||
})
|
||||
}
|
45
crates/nu-cli/tests/commands/path/expand.rs
Normal file
45
crates/nu-cli/tests/commands/path/expand.rs
Normal file
@ -0,0 +1,45 @@
|
||||
use nu_test_support::fs::Stub::EmptyFile;
|
||||
use nu_test_support::playground::Playground;
|
||||
use nu_test_support::{nu, pipeline};
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[test]
|
||||
fn expands_path_with_dot() {
|
||||
Playground::setup("path_expand_1", |dirs, sandbox| {
|
||||
sandbox
|
||||
.within("menu")
|
||||
.with_files(vec![EmptyFile("spam.txt")]);
|
||||
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
r#"
|
||||
echo "menu/./spam.txt"
|
||||
| path expand
|
||||
"#
|
||||
));
|
||||
|
||||
let expected = dirs.test.join("menu").join("spam.txt");
|
||||
assert_eq!(PathBuf::from(actual.out), expected);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expands_path_with_double_dot() {
|
||||
Playground::setup("path_expand_2", |dirs, sandbox| {
|
||||
sandbox
|
||||
.within("menu")
|
||||
.with_files(vec![EmptyFile("spam.txt")]);
|
||||
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
r#"
|
||||
echo "menu/../menu/spam.txt"
|
||||
| path expand
|
||||
"#
|
||||
));
|
||||
|
||||
let expected = dirs.test.join("menu").join("spam.txt");
|
||||
assert_eq!(PathBuf::from(actual.out), expected);
|
||||
})
|
||||
}
|
37
crates/nu-cli/tests/commands/path/extension.rs
Normal file
37
crates/nu-cli/tests/commands/path/extension.rs
Normal file
@ -0,0 +1,37 @@
|
||||
use nu_test_support::{nu, pipeline};
|
||||
|
||||
#[test]
|
||||
fn returns_extension_of_path_ending_with_dot() {
|
||||
let actual = nu!(
|
||||
cwd: "tests", pipeline(
|
||||
r#"
|
||||
echo "bacon." | path extension
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn replaces_extension_with_dot_of_path_ending_with_dot() {
|
||||
let actual = nu!(
|
||||
cwd: "tests", pipeline(
|
||||
r#"
|
||||
echo "bacon." | path extension -r .egg
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "bacon..egg");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn replaces_extension_of_empty_path() {
|
||||
let actual = nu!(
|
||||
cwd: "tests", pipeline(
|
||||
r#"
|
||||
echo "" | path extension -r egg
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "");
|
||||
}
|
95
crates/nu-cli/tests/commands/path/filestem.rs
Normal file
95
crates/nu-cli/tests/commands/path/filestem.rs
Normal file
@ -0,0 +1,95 @@
|
||||
use nu_test_support::{nu, pipeline};
|
||||
|
||||
use super::join_path_sep;
|
||||
|
||||
#[test]
|
||||
fn returns_filestem_of_dot() {
|
||||
let actual = nu!(
|
||||
cwd: "tests", pipeline(
|
||||
r#"
|
||||
echo "menu/eggs/."
|
||||
| path filestem
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "eggs");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn returns_filestem_of_double_dot() {
|
||||
let actual = nu!(
|
||||
cwd: "tests", pipeline(
|
||||
r#"
|
||||
echo "menu/eggs/.."
|
||||
| path filestem
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn returns_filestem_of_path_with_empty_prefix() {
|
||||
let actual = nu!(
|
||||
cwd: "tests", pipeline(
|
||||
r#"
|
||||
echo "menu/spam.txt"
|
||||
| path filestem -p ""
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "spam");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn returns_filestem_of_path_with_empty_suffix() {
|
||||
let actual = nu!(
|
||||
cwd: "tests", pipeline(
|
||||
r#"
|
||||
echo "menu/spam.txt"
|
||||
| path filestem -s ""
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "spam.txt");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn returns_filestem_of_path_with_empty_prefix_and_suffix() {
|
||||
let actual = nu!(
|
||||
cwd: "tests", pipeline(
|
||||
r#"
|
||||
echo "menu/spam.txt"
|
||||
| path filestem -p "" -s ""
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "spam.txt");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn returns_filestem_with_wrong_prefix_and_suffix() {
|
||||
let actual = nu!(
|
||||
cwd: "tests", pipeline(
|
||||
r#"
|
||||
echo "menu/spam.txt"
|
||||
| path filestem -p "bacon" -s "eggs"
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "spam.txt");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn replaces_filestem_stripped_to_dot() {
|
||||
let actual = nu!(
|
||||
cwd: "tests", pipeline(
|
||||
r#"
|
||||
echo "menu/spam.txt"
|
||||
| path filestem -p "spam" -s "txt" -r ".eggs."
|
||||
"#
|
||||
));
|
||||
|
||||
let expected = join_path_sep(&["menu", "spam.eggs.txt"]);
|
||||
assert_eq!(actual.out, expected);
|
||||
}
|
33
crates/nu-cli/tests/commands/path/mod.rs
Normal file
33
crates/nu-cli/tests/commands/path/mod.rs
Normal file
@ -0,0 +1,33 @@
|
||||
mod basename;
|
||||
mod dirname;
|
||||
mod exists;
|
||||
mod expand;
|
||||
mod extension;
|
||||
mod filestem;
|
||||
mod type_;
|
||||
|
||||
use std::path::MAIN_SEPARATOR;
|
||||
|
||||
/// Helper function that joins string literals with '/' or '\', based on host OS
|
||||
fn join_path_sep(pieces: &[&str]) -> String {
|
||||
let sep_string = String::from(MAIN_SEPARATOR);
|
||||
pieces.join(&sep_string)
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[test]
|
||||
fn joins_path_on_windows() {
|
||||
let pieces = ["sausage", "bacon", "spam"];
|
||||
let actual = join_path_sep(&pieces);
|
||||
|
||||
assert_eq!(&actual, "sausage\\bacon\\spam");
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
#[test]
|
||||
fn joins_path_on_other_than_windows() {
|
||||
let pieces = ["sausage", "bacon", "spam"];
|
||||
let actual = join_path_sep(&pieces);
|
||||
|
||||
assert_eq!(&actual, "sausage/bacon/spam");
|
||||
}
|
54
crates/nu-cli/tests/commands/path/type_.rs
Normal file
54
crates/nu-cli/tests/commands/path/type_.rs
Normal file
@ -0,0 +1,54 @@
|
||||
use nu_test_support::fs::Stub::EmptyFile;
|
||||
use nu_test_support::playground::Playground;
|
||||
use nu_test_support::{nu, pipeline};
|
||||
|
||||
#[test]
|
||||
fn returns_type_of_missing_file() {
|
||||
let actual = nu!(
|
||||
cwd: "tests", pipeline(
|
||||
r#"
|
||||
echo "spam.txt"
|
||||
| path type
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn returns_type_of_existing_file() {
|
||||
Playground::setup("path_expand_1", |dirs, sandbox| {
|
||||
sandbox
|
||||
.within("menu")
|
||||
.with_files(vec![EmptyFile("spam.txt")]);
|
||||
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
r#"
|
||||
echo "menu"
|
||||
| path type
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "Dir");
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn returns_type_of_existing_directory() {
|
||||
Playground::setup("path_expand_1", |dirs, sandbox| {
|
||||
sandbox
|
||||
.within("menu")
|
||||
.with_files(vec![EmptyFile("spam.txt")]);
|
||||
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
r#"
|
||||
echo "menu/spam.txt"
|
||||
| path type
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "File");
|
||||
})
|
||||
}
|
Loading…
Reference in New Issue
Block a user