ltrim and rtrim for string (#2262)

* Trim string from left and right

* Move trim to folder

* fmt

* Clippy
This commit is contained in:
k-brk 2020-07-26 20:09:35 +02:00 committed by GitHub
parent 7e2c627044
commit 5e0a9aecaa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 372 additions and 172 deletions

View File

@ -311,6 +311,8 @@ pub fn create_default_context(
whole_stream_command(StrSet),
whole_stream_command(StrToDatetime),
whole_stream_command(StrTrim),
whole_stream_command(StrTrimLeft),
whole_stream_command(StrTrimRight),
whole_stream_command(StrCollect),
whole_stream_command(StrLength),
whole_stream_command(StrReverse),

View File

@ -229,7 +229,8 @@ pub(crate) use split::{Split, SplitChars, SplitColumn, SplitRow};
pub(crate) use split_by::SplitBy;
pub(crate) use str_::{
Str, StrCapitalize, StrCollect, StrDowncase, StrFindReplace, StrFrom, StrLength, StrReverse,
StrSet, StrSubstring, StrToDatetime, StrToDecimal, StrToInteger, StrTrim, StrUpcase,
StrSet, StrSubstring, StrToDatetime, StrToDecimal, StrToInteger, StrTrim, StrTrimLeft,
StrTrimRight, StrUpcase,
};
pub(crate) use table::Table;
pub(crate) use tags::Tags;

View File

@ -1,4 +1,3 @@
use crate::commands::str_::trim::trim_char;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
@ -196,7 +195,7 @@ fn format_decimal(mut decimal: BigDecimal, digits: Option<u64>, group_digits: bo
.take(n as usize)
.collect()
} else {
trim_char(&dec_part, '0', false, true)
String::from(dec_part.trim_end_matches('0'))
};
let format_default_loc = |int_part: BigInt| {

View File

@ -27,5 +27,7 @@ pub use substring::SubCommand as StrSubstring;
pub use to_datetime::SubCommand as StrToDatetime;
pub use to_decimal::SubCommand as StrToDecimal;
pub use to_integer::SubCommand as StrToInteger;
pub use trim::SubCommand as StrTrim;
pub use trim::Trim as StrTrim;
pub use trim::TrimLeft as StrTrimLeft;
pub use trim::TrimRight as StrTrimRight;
pub use upcase::SubCommand as StrUpcase;

View File

@ -1,168 +0,0 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::ShellTypeName;
use nu_protocol::{
ColumnPath, Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
};
use nu_source::{Tag, Tagged};
use nu_value_ext::ValueExt;
#[derive(Deserialize)]
struct Arguments {
rest: Vec<ColumnPath>,
#[serde(rename(deserialize = "char"))]
char_: Option<Tagged<char>>,
}
pub struct SubCommand;
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"str trim"
}
fn signature(&self) -> Signature {
Signature::build("str trim")
.rest(
SyntaxShape::ColumnPath,
"optionally trim text by column paths",
)
.named(
"char",
SyntaxShape::String,
"character to trim (default: whitespace)",
Some('c'),
)
}
fn usage(&self) -> &str {
"trims text"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
operate(args, registry).await
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Trim whitespace",
example: "echo 'Nu shell ' | str trim",
result: Some(vec![Value::from("Nu shell")]),
},
Example {
description: "Trim a specific character",
example: "echo '=== Nu shell ===' | str trim -c '=' | str trim",
result: Some(vec![Value::from("Nu shell")]),
},
]
}
}
async fn operate(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let (Arguments { rest, char_ }, input) = args.process(&registry).await?;
let column_paths: Vec<_> = rest;
let to_trim = char_.map(|tagged| tagged.item);
Ok(input
.map(move |v| {
if column_paths.is_empty() {
ReturnSuccess::value(action(&v, v.tag(), to_trim)?)
} else {
let mut ret = v;
for path in &column_paths {
ret = ret.swap_data_by_column_path(
path,
Box::new(move |old| action(old, old.tag(), to_trim)),
)?;
}
ReturnSuccess::value(ret)
}
})
.to_output_stream())
}
fn action(input: &Value, tag: impl Into<Tag>, char_: Option<char>) -> Result<Value, ShellError> {
match &input.value {
UntaggedValue::Primitive(Primitive::Line(s))
| UntaggedValue::Primitive(Primitive::String(s)) => {
Ok(UntaggedValue::string(match char_ {
None => String::from(s.trim()),
Some(ch) => trim_char(s, ch, true, true),
})
.into_value(tag))
}
other => {
let got = format!("got {}", other.type_name());
Err(ShellError::labeled_error(
"value is not string",
got,
tag.into().span,
))
}
}
}
pub fn trim_char(from: &str, to_trim: char, leading: bool, trailing: bool) -> String {
let mut trimmed = String::from("");
let mut backlog = String::from("");
let mut at_left = true;
from.chars().for_each(|ch| match ch {
c if c == to_trim => {
if !(leading && at_left) {
if trailing {
backlog.push(c)
} else {
trimmed.push(c)
}
}
}
other => {
at_left = false;
if trailing {
trimmed.push_str(backlog.as_str());
backlog = String::from("");
}
trimmed.push(other);
}
});
trimmed
}
#[cfg(test)]
mod tests {
use super::{action, SubCommand};
use nu_plugin::test_helpers::value::string;
use nu_source::Tag;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(SubCommand {})
}
#[test]
fn trims() {
let word = string("andres ");
let expected = string("andres");
let actual = action(&word, Tag::unknown(), None).unwrap();
assert_eq!(actual, expected);
}
}

View File

@ -0,0 +1,81 @@
mod trim_both_ends;
mod trim_left;
mod trim_right;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::ShellTypeName;
use nu_protocol::{ColumnPath, Primitive, ReturnSuccess, UntaggedValue, Value};
use nu_source::{Tag, Tagged};
use nu_value_ext::ValueExt;
pub use trim_both_ends::SubCommand as Trim;
pub use trim_left::SubCommand as TrimLeft;
pub use trim_right::SubCommand as TrimRight;
#[derive(Deserialize)]
struct Arguments {
rest: Vec<ColumnPath>,
#[serde(rename(deserialize = "char"))]
char_: Option<Tagged<char>>,
}
pub async fn operate<F>(
args: CommandArgs,
registry: &CommandRegistry,
trim_operation: &'static F,
) -> Result<OutputStream, ShellError>
where
F: Fn(&str, Option<char>) -> String + Send + Sync + 'static,
{
let registry = registry.clone();
let (Arguments { rest, char_ }, input) = args.process(&registry).await?;
let column_paths: Vec<_> = rest;
let to_trim = char_.map(|tagged| tagged.item);
Ok(input
.map(move |v| {
if column_paths.is_empty() {
ReturnSuccess::value(action(&v, v.tag(), to_trim, &trim_operation)?)
} else {
let mut ret = v;
for path in &column_paths {
ret = ret.swap_data_by_column_path(
path,
Box::new(move |old| action(old, old.tag(), to_trim, &trim_operation)),
)?;
}
ReturnSuccess::value(ret)
}
})
.to_output_stream())
}
pub fn action<F>(
input: &Value,
tag: impl Into<Tag>,
char_: Option<char>,
trim_operation: &F,
) -> Result<Value, ShellError>
where
F: Fn(&str, Option<char>) -> String + Send + Sync + 'static,
{
match &input.value {
UntaggedValue::Primitive(Primitive::Line(s))
| UntaggedValue::Primitive(Primitive::String(s)) => {
Ok(UntaggedValue::string(trim_operation(s, char_)).into_value(tag))
}
other => {
let got = format!("got {}", other.type_name());
Err(ShellError::labeled_error(
"value is not string",
got,
tag.into().span,
))
}
}
}

View File

@ -0,0 +1,94 @@
use super::operate;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{Signature, SyntaxShape, Value};
pub struct SubCommand;
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"str trim"
}
fn signature(&self) -> Signature {
Signature::build("str trim")
.rest(
SyntaxShape::ColumnPath,
"optionally trim text by column paths",
)
.named(
"char",
SyntaxShape::String,
"character to trim (default: whitespace)",
Some('c'),
)
}
fn usage(&self) -> &str {
"trims text"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
operate(args, registry, &trim).await
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Trim whitespace",
example: "echo 'Nu shell ' | str trim",
result: Some(vec![Value::from("Nu shell")]),
},
Example {
description: "Trim a specific character",
example: "echo '=== Nu shell ===' | str trim -c '=' | str trim",
result: Some(vec![Value::from("Nu shell")]),
},
]
}
}
fn trim(s: &str, char_: Option<char>) -> String {
match char_ {
None => String::from(s.trim()),
Some(ch) => String::from(s.trim_matches(ch)),
}
}
#[cfg(test)]
mod tests {
use super::{trim, SubCommand};
use crate::commands::str_::trim::action;
use nu_plugin::test_helpers::value::string;
use nu_source::Tag;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(SubCommand {})
}
#[test]
fn trims() {
let word = string("andres ");
let expected = string("andres");
let actual = action(&word, Tag::unknown(), None, &trim).unwrap();
assert_eq!(actual, expected);
}
#[test]
fn trims_custom_character_both_ends() {
let word = string("!#andres#!");
let expected = string("#andres#");
let actual = action(&word, Tag::unknown(), Some('!'), &trim).unwrap();
assert_eq!(actual, expected);
}
}

View File

@ -0,0 +1,94 @@
use super::operate;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{Signature, SyntaxShape, Value};
pub struct SubCommand;
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"str ltrim"
}
fn signature(&self) -> Signature {
Signature::build("str ltrim")
.rest(
SyntaxShape::ColumnPath,
"optionally trim text starting from the beginning by column paths",
)
.named(
"char",
SyntaxShape::String,
"character to trim (default: whitespace)",
Some('c'),
)
}
fn usage(&self) -> &str {
"trims whitespace or character from the beginning of text"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
operate(args, registry, &trim_left).await
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Trim whitespace from the beginning of string",
example: "echo ' Nu shell ' | str ltrim",
result: Some(vec![Value::from("Nu shell ")]),
},
Example {
description: "Trim a specific character",
example: "echo '=== Nu shell ===' | str ltrim -c '='",
result: Some(vec![Value::from(" Nu shell ===")]),
},
]
}
}
fn trim_left(s: &str, char_: Option<char>) -> String {
match char_ {
None => String::from(s.trim_start()),
Some(ch) => String::from(s.trim_start_matches(ch)),
}
}
#[cfg(test)]
mod tests {
use super::{trim_left, SubCommand};
use crate::commands::str_::trim::action;
use nu_plugin::test_helpers::value::string;
use nu_source::Tag;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(SubCommand {})
}
#[test]
fn trims_whitespace_from_left() {
let word = string(" andres ");
let expected = string("andres ");
let actual = action(&word, Tag::unknown(), None, &trim_left).unwrap();
assert_eq!(actual, expected);
}
#[test]
fn trims_custom_chars_from_left() {
let word = string("!!! andres !!!");
let expected = string(" andres !!!");
let actual = action(&word, Tag::unknown(), Some('!'), &trim_left).unwrap();
assert_eq!(actual, expected);
}
}

View File

@ -0,0 +1,95 @@
use super::operate;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{Signature, SyntaxShape, Value};
pub struct SubCommand;
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"str rtrim"
}
fn signature(&self) -> Signature {
Signature::build("str rtrim")
.rest(
SyntaxShape::ColumnPath,
"optionally trim text starting from the end by column paths",
)
.named(
"char",
SyntaxShape::String,
"character to trim (default: whitespace)",
Some('c'),
)
}
fn usage(&self) -> &str {
"trims whitespace or character from the end of text"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
operate(args, registry, &trim_right).await
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Trim whitespace from the end of string",
example: "echo ' Nu shell ' | str rtrim",
result: Some(vec![Value::from(" Nu shell")]),
},
Example {
description: "Trim a specific character",
example: "echo '=== Nu shell ===' | str rtrim -c '='",
result: Some(vec![Value::from("=== Nu shell ")]),
},
]
}
}
fn trim_right(s: &str, char_: Option<char>) -> String {
match char_ {
None => String::from(s.trim_end()),
Some(ch) => String::from(s.trim_end_matches(ch)),
}
}
#[cfg(test)]
mod tests {
use super::{trim_right, SubCommand};
use crate::commands::str_::trim::action;
use nu_plugin::test_helpers::value::string;
use nu_source::Tag;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(SubCommand {})
}
#[test]
fn trims_whitespace_from_right() {
let word = string(" andres ");
let expected = string(" andres");
let actual = action(&word, Tag::unknown(), None, &trim_right).unwrap();
assert_eq!(actual, expected);
}
#[test]
fn trims_custom_chars_from_right() {
let word = string("#@! andres !@#");
let expected = string("#@! andres !@");
let actual = action(&word, Tag::unknown(), Some('#'), &trim_right).unwrap();
assert_eq!(actual, expected);
}
}