mirror of
https://github.com/nushell/nushell
synced 2025-01-15 22:54:16 +00:00
WIP: Path utility commands (#2255)
* Add new path commands basename, expand and extension. Currently there is no real error handling. expand returns the initial path if it didn't work, the others return empty string * Optionally apply to path
This commit is contained in:
parent
4347339e9a
commit
7e2c627044
7 changed files with 297 additions and 0 deletions
|
@ -411,6 +411,11 @@ pub fn create_default_context(
|
||||||
whole_stream_command(RandomDice),
|
whole_stream_command(RandomDice),
|
||||||
#[cfg(feature = "uuid_crate")]
|
#[cfg(feature = "uuid_crate")]
|
||||||
whole_stream_command(RandomUUID),
|
whole_stream_command(RandomUUID),
|
||||||
|
// Path
|
||||||
|
whole_stream_command(PathCommand),
|
||||||
|
whole_stream_command(PathExtension),
|
||||||
|
whole_stream_command(PathBasename),
|
||||||
|
whole_stream_command(PathExpand),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
#[cfg(feature = "clipboard")]
|
#[cfg(feature = "clipboard")]
|
||||||
|
|
|
@ -76,6 +76,7 @@ pub(crate) mod next;
|
||||||
pub(crate) mod nth;
|
pub(crate) mod nth;
|
||||||
pub(crate) mod open;
|
pub(crate) mod open;
|
||||||
pub(crate) mod parse;
|
pub(crate) mod parse;
|
||||||
|
pub(crate) mod path;
|
||||||
pub(crate) mod pivot;
|
pub(crate) mod pivot;
|
||||||
pub(crate) mod plugin;
|
pub(crate) mod plugin;
|
||||||
pub(crate) mod prepend;
|
pub(crate) mod prepend;
|
||||||
|
@ -203,6 +204,7 @@ pub(crate) use next::Next;
|
||||||
pub(crate) use nth::Nth;
|
pub(crate) use nth::Nth;
|
||||||
pub(crate) use open::Open;
|
pub(crate) use open::Open;
|
||||||
pub(crate) use parse::Parse;
|
pub(crate) use parse::Parse;
|
||||||
|
pub(crate) use path::{PathBasename, PathCommand, PathExpand, PathExtension};
|
||||||
pub(crate) use pivot::Pivot;
|
pub(crate) use pivot::Pivot;
|
||||||
pub(crate) use prepend::Prepend;
|
pub(crate) use prepend::Prepend;
|
||||||
pub(crate) use prev::Previous;
|
pub(crate) use prev::Previous;
|
||||||
|
|
60
crates/nu-cli/src/commands/path/basename.rs
Normal file
60
crates/nu-cli/src/commands/path/basename.rs
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
use super::{operate, DefaultArguments};
|
||||||
|
use crate::commands::WholeStreamCommand;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use nu_errors::ShellError;
|
||||||
|
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
pub struct PathBasename;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl WholeStreamCommand for PathBasename {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"path basename"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("path basename")
|
||||||
|
.rest(SyntaxShape::ColumnPath, "optionally operate by path")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"gets the filename of a path"
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(
|
||||||
|
&self,
|
||||||
|
args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
let (DefaultArguments { rest }, input) = args.process(®istry).await?;
|
||||||
|
operate(input, rest, &action).await
|
||||||
|
}
|
||||||
|
|
||||||
|
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")]),
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn action(path: &Path) -> UntaggedValue {
|
||||||
|
UntaggedValue::string(match path.file_name() {
|
||||||
|
Some(filename) => filename.to_string_lossy().to_string(),
|
||||||
|
_ => "".to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::PathBasename;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn examples_work_as_expected() {
|
||||||
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
|
test_examples(PathBasename {})
|
||||||
|
}
|
||||||
|
}
|
46
crates/nu-cli/src/commands/path/command.rs
Normal file
46
crates/nu-cli/src/commands/path/command.rs
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
use crate::commands::WholeStreamCommand;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use nu_errors::ShellError;
|
||||||
|
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
|
||||||
|
|
||||||
|
pub struct Path;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl WholeStreamCommand for Path {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"path"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("path")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Apply path function"
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(
|
||||||
|
&self,
|
||||||
|
_args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
let registry = registry.clone();
|
||||||
|
|
||||||
|
Ok(OutputStream::one(ReturnSuccess::value(
|
||||||
|
UntaggedValue::string(crate::commands::help::get_help(&Path, ®istry))
|
||||||
|
.into_value(Tag::unknown()),
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::Path;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn examples_work_as_expected() {
|
||||||
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
|
test_examples(Path {})
|
||||||
|
}
|
||||||
|
}
|
50
crates/nu-cli/src/commands/path/expand.rs
Normal file
50
crates/nu-cli/src/commands/path/expand.rs
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
use super::{operate, DefaultArguments};
|
||||||
|
use crate::commands::WholeStreamCommand;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use nu_errors::ShellError;
|
||||||
|
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
pub struct PathExpand;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl WholeStreamCommand for PathExpand {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"path expand"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("path expand").rest(SyntaxShape::ColumnPath, "optionally operate by path")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"expands the path to its absolute form"
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(
|
||||||
|
&self,
|
||||||
|
args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
let (DefaultArguments { rest }, input) = args.process(®istry).await?;
|
||||||
|
operate(input, rest, &action).await
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![Example {
|
||||||
|
description: "Expand relative directories",
|
||||||
|
example: "echo '/home/joe/foo/../bar' | path expand",
|
||||||
|
result: Some(vec![Value::from("/home/joe/bar")]),
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn action(path: &Path) -> 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(),
|
||||||
|
})
|
||||||
|
}
|
67
crates/nu-cli/src/commands/path/extension.rs
Normal file
67
crates/nu-cli/src/commands/path/extension.rs
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
use super::{operate, DefaultArguments};
|
||||||
|
use crate::commands::WholeStreamCommand;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use nu_errors::ShellError;
|
||||||
|
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
pub struct PathExtension;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl WholeStreamCommand for PathExtension {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"path extension"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("path extension")
|
||||||
|
.rest(SyntaxShape::ColumnPath, "optionally operate by path")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"gets the extension of a path"
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(
|
||||||
|
&self,
|
||||||
|
args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
let (DefaultArguments { rest }, input) = args.process(®istry).await?;
|
||||||
|
operate(input, rest, &action).await
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Get extension of a path",
|
||||||
|
example: "echo 'test.txt' | path extension",
|
||||||
|
result: Some(vec![Value::from("txt")]),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "You get an empty string if there is no extension",
|
||||||
|
example: "echo 'test' | path extension",
|
||||||
|
result: Some(vec![Value::from("")]),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn action(path: &Path) -> UntaggedValue {
|
||||||
|
UntaggedValue::string(match path.extension() {
|
||||||
|
Some(ext) => ext.to_string_lossy().to_string(),
|
||||||
|
_ => "".to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::PathExtension;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn examples_work_as_expected() {
|
||||||
|
use crate::examples::test as test_examples;
|
||||||
|
|
||||||
|
test_examples(PathExtension {})
|
||||||
|
}
|
||||||
|
}
|
67
crates/nu-cli/src/commands/path/mod.rs
Normal file
67
crates/nu-cli/src/commands/path/mod.rs
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
mod basename;
|
||||||
|
mod command;
|
||||||
|
mod expand;
|
||||||
|
mod extension;
|
||||||
|
|
||||||
|
use crate::prelude::*;
|
||||||
|
use nu_errors::ShellError;
|
||||||
|
use nu_protocol::{ColumnPath, Primitive, ReturnSuccess, ShellTypeName, UntaggedValue, Value};
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
pub use basename::PathBasename;
|
||||||
|
pub use command::Path as PathCommand;
|
||||||
|
pub use expand::PathExpand;
|
||||||
|
pub use extension::PathExtension;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct DefaultArguments {
|
||||||
|
rest: Vec<ColumnPath>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_value<F>(action: &F, v: &Value) -> Result<Value, ShellError>
|
||||||
|
where
|
||||||
|
F: Fn(&Path) -> UntaggedValue + Send + 'static,
|
||||||
|
{
|
||||||
|
let v = match &v.value {
|
||||||
|
UntaggedValue::Primitive(Primitive::Path(buf)) => action(buf).into_value(v.tag()),
|
||||||
|
UntaggedValue::Primitive(Primitive::String(s))
|
||||||
|
| UntaggedValue::Primitive(Primitive::Line(s)) => action(s.as_ref()).into_value(v.tag()),
|
||||||
|
other => {
|
||||||
|
let got = format!("got {}", other.type_name());
|
||||||
|
return Err(ShellError::labeled_error(
|
||||||
|
"value is not string or path",
|
||||||
|
got,
|
||||||
|
v.tag().span,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn operate<F>(
|
||||||
|
input: crate::InputStream,
|
||||||
|
paths: Vec<ColumnPath>,
|
||||||
|
action: &'static F,
|
||||||
|
) -> Result<OutputStream, ShellError>
|
||||||
|
where
|
||||||
|
F: Fn(&Path) -> UntaggedValue + Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
Ok(input
|
||||||
|
.map(move |v| {
|
||||||
|
if paths.is_empty() {
|
||||||
|
ReturnSuccess::value(handle_value(&action, &v)?)
|
||||||
|
} else {
|
||||||
|
let mut ret = v;
|
||||||
|
|
||||||
|
for path in &paths {
|
||||||
|
ret = ret.swap_data_by_column_path(
|
||||||
|
path,
|
||||||
|
Box::new(move |old| handle_value(&action, &old)),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
ReturnSuccess::value(ret)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.to_output_stream())
|
||||||
|
}
|
Loading…
Reference in a new issue