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:
Matt Hall 2020-07-25 20:29:15 +01:00 committed by GitHub
parent 4347339e9a
commit 7e2c627044
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 297 additions and 0 deletions

View file

@ -411,6 +411,11 @@ pub fn create_default_context(
whole_stream_command(RandomDice),
#[cfg(feature = "uuid_crate")]
whole_stream_command(RandomUUID),
// Path
whole_stream_command(PathCommand),
whole_stream_command(PathExtension),
whole_stream_command(PathBasename),
whole_stream_command(PathExpand),
]);
#[cfg(feature = "clipboard")]

View file

@ -76,6 +76,7 @@ pub(crate) mod next;
pub(crate) mod nth;
pub(crate) mod open;
pub(crate) mod parse;
pub(crate) mod path;
pub(crate) mod pivot;
pub(crate) mod plugin;
pub(crate) mod prepend;
@ -203,6 +204,7 @@ pub(crate) use next::Next;
pub(crate) use nth::Nth;
pub(crate) use open::Open;
pub(crate) use parse::Parse;
pub(crate) use path::{PathBasename, PathCommand, PathExpand, PathExtension};
pub(crate) use pivot::Pivot;
pub(crate) use prepend::Prepend;
pub(crate) use prev::Previous;

View 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(&registry).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 {})
}
}

View 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, &registry))
.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 {})
}
}

View 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(&registry).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(),
})
}

View 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(&registry).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 {})
}
}

View 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())
}