str substring additions. (#2140)

This commit is contained in:
Andrés N. Robalino 2020-07-08 04:45:45 -05:00 committed by GitHub
parent 28be39494c
commit 0fdb9ac5e2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -5,14 +5,16 @@ use nu_protocol::ShellTypeName;
use nu_protocol::{
ColumnPath, Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
};
use nu_source::{Tag, Tagged};
use nu_value_ext::ValueExt;
use nu_source::Tag;
use nu_value_ext::{as_string, ValueExt};
use std::cmp;
use std::cmp::Ordering;
use std::convert::TryInto;
#[derive(Deserialize)]
struct Arguments {
range: Tagged<String>,
range: Value,
rest: Vec<ColumnPath>,
}
@ -28,8 +30,8 @@ impl WholeStreamCommand for SubCommand {
Signature::build("str substring")
.required(
"range",
SyntaxShape::String,
"the indexes to substring \"start, end\"",
SyntaxShape::Any,
"the indexes to substring [start end]",
)
.rest(
SyntaxShape::ColumnPath,
@ -53,9 +55,19 @@ impl WholeStreamCommand for SubCommand {
vec![
Example {
description: "Get a substring from the text",
example: "echo 'good nushell' | str substring [5 12]",
result: Some(vec![Value::from("nushell")]),
},
Example {
description: "Alternatively, you can use the form",
example: "echo 'good nushell' | str substring '5,12'",
result: Some(vec![Value::from("nushell")]),
},
Example {
description: "Get the last characters from the string",
example: "echo 'good nushell' | str substring ',-5'",
result: Some(vec![Value::from("shell")]),
},
Example {
description: "Get the remaining characters from a starting index",
example: "echo 'good nushell' | str substring '5,'",
@ -71,7 +83,15 @@ impl WholeStreamCommand for SubCommand {
}
#[derive(Clone)]
struct Substring(usize, usize);
struct Substring(isize, isize);
impl From<(isize, isize)> for Substring {
fn from(input: (isize, isize)) -> Substring {
Substring(input.0, input.1)
}
}
struct SubstringText(String, String);
async fn operate(
args: CommandArgs,
@ -82,41 +102,8 @@ async fn operate(
let (Arguments { range, rest }, input) = args.process(&registry).await?;
let v: Vec<&str> = range.item.split(',').collect();
let start = match v[0] {
"" => 0,
_ => v[0].trim().parse().map_err(|_| {
ShellError::labeled_error(
"could not perform substring",
"could not perform substring",
name.span,
)
})?,
};
let end = match v[1] {
"" => usize::max_value(),
_ => v[1].trim().parse().map_err(|_| {
ShellError::labeled_error(
"could not perform substring",
"could not perform substring",
name.span,
)
})?,
};
if start > end {
return Err(ShellError::labeled_error(
"End must be greater than or equal to Start",
"End must be greater than or equal to Start",
name.span,
));
}
let options = Substring(start, end);
let column_paths: Vec<_> = rest;
let options = process_arguments(range, name)?.into();
Ok(input
.map(move |v| {
@ -153,35 +140,187 @@ async fn operate(
}
fn action(input: &Value, options: &Substring, tag: impl Into<Tag>) -> Result<Value, ShellError> {
let tag = tag.into();
match &input.value {
UntaggedValue::Primitive(Primitive::Line(s))
| UntaggedValue::Primitive(Primitive::String(s)) => {
let start = options.0;
let end: usize = cmp::min(options.1, s.len());
let len: isize = s.len().try_into().map_err(|_| {
ShellError::labeled_error(
"could not perform substring",
"could not perform substring",
tag.span,
)
})?;
let out = {
if start > s.len() - 1 {
UntaggedValue::string("")
} else {
UntaggedValue::string(
s.chars().skip(start).take(end - start).collect::<String>(),
)
let start: isize = options.0.try_into().map_err(|_| {
ShellError::labeled_error(
"could not perform substring",
"could not perform substring",
tag.span,
)
})?;
let end = options.1;
if start < len && end >= 0 {
match start.cmp(&end) {
Ordering::Equal => Ok(UntaggedValue::string("").into_value(tag)),
Ordering::Greater => Err(ShellError::labeled_error(
"End must be greater than or equal to Start",
"End must be greater than or equal to Start",
tag.span,
)),
Ordering::Less => {
let end: isize = cmp::min(options.1, len);
Ok(UntaggedValue::string(
s.chars()
.skip(start as usize)
.take((end - start) as usize)
.collect::<String>(),
)
.into_value(tag))
}
}
};
} else if start >= 0 && end <= 0 {
let end = options.1.abs();
let reversed = s
.chars()
.skip(start as usize)
.take((len - start) as usize)
.collect::<String>();
Ok(out.into_value(tag))
let reversed = if start == 0 {
reversed
} else {
s.chars().take(start as usize).collect::<String>()
};
let reversed = reversed
.chars()
.rev()
.take(end as usize)
.collect::<String>();
Ok(
UntaggedValue::string(reversed.chars().rev().collect::<String>())
.into_value(tag),
)
} else {
Ok(UntaggedValue::string("").into_value(tag))
}
}
other => {
let got = format!("got {}", other.type_name());
Err(ShellError::labeled_error(
"value is not string",
got,
tag.into().span,
tag.span,
))
}
}
}
fn process_arguments(range: Value, name: impl Into<Tag>) -> Result<(isize, isize), ShellError> {
let name = name.into();
let search = match &range.value {
UntaggedValue::Table(indexes) => {
if indexes.len() > 2 {
Err(ShellError::labeled_error(
"could not perform substring",
"could not perform substring",
name.span,
))
} else {
let idx: Vec<String> = indexes
.iter()
.map(|v| as_string(v).unwrap_or_else(|_| String::from("")))
.collect();
let start = idx
.get(0)
.ok_or_else(|| {
ShellError::labeled_error(
"could not perform substring",
"could not perform substring",
name.span,
)
})?
.to_string();
let end = idx
.get(1)
.ok_or_else(|| {
ShellError::labeled_error(
"could not perform substring",
"could not perform substring",
name.span,
)
})?
.to_string();
Ok(SubstringText(start, end))
}
}
UntaggedValue::Primitive(Primitive::String(indexes)) => {
let idx: Vec<&str> = indexes.split(',').collect();
let start = idx
.get(0)
.ok_or_else(|| {
ShellError::labeled_error(
"could not perform substring",
"could not perform substring",
name.span,
)
})?
.to_string();
let end = idx
.get(1)
.ok_or_else(|| {
ShellError::labeled_error(
"could not perform substring",
"could not perform substring",
name.span,
)
})?
.to_string();
Ok(SubstringText(start, end))
}
_ => Err(ShellError::labeled_error(
"could not perform substring",
"could not perform substring",
name.span,
)),
}?;
let start = match &search {
SubstringText(start, _) if start == "" || start == "_" => 0,
SubstringText(start, _) => start.trim().parse().map_err(|_| {
ShellError::labeled_error(
"could not perform substring",
"could not perform substring",
name.span,
)
})?,
};
let end = match &search {
SubstringText(_, end) if end == "" || end == "_" => isize::max_value(),
SubstringText(_, end) => end.trim().parse().map_err(|_| {
ShellError::labeled_error(
"could not perform substring",
"could not perform substring",
name.span,
)
})?,
};
Ok((start, end))
}
#[cfg(test)]
mod tests {
use super::{action, SubCommand, Substring};
@ -195,14 +334,55 @@ mod tests {
test_examples(SubCommand {})
}
struct Expectation<'a> {
options: (isize, isize),
expected: &'a str,
}
impl Expectation<'_> {
fn options(&self) -> Substring {
Substring(self.options.0, self.options.1)
}
}
fn expectation(word: &str, indexes: (isize, isize)) -> Expectation {
Expectation {
options: indexes,
expected: word,
}
}
#[test]
fn given_start_and_end_indexes() {
let word = string("andresS");
let expected = string("andres");
fn substrings_indexes() {
let word = string("andres");
let substring_options = Substring(0, 6);
let cases = vec![
expectation("a", (0, 1)),
expectation("an", (0, 2)),
expectation("and", (0, 3)),
expectation("andr", (0, 4)),
expectation("andre", (0, 5)),
expectation("andres", (0, 6)),
expectation("andres", (0, -6)),
expectation("ndres", (0, -5)),
expectation("dres", (0, -4)),
expectation("res", (0, -3)),
expectation("es", (0, -2)),
expectation("s", (0, -1)),
expectation("", (6, 0)),
expectation("s", (6, -1)),
expectation("es", (6, -2)),
expectation("res", (6, -3)),
expectation("dres", (6, -4)),
expectation("ndres", (6, -5)),
expectation("andres", (6, -6)),
];
let actual = action(&word, &substring_options, Tag::unknown()).unwrap();
assert_eq!(actual, expected);
for expectation in cases.iter() {
let expected = expectation.expected;
let actual = action(&word, &expectation.options(), Tag::unknown()).unwrap();
assert_eq!(actual, string(expected));
}
}
}