mirror of
https://github.com/nushell/nushell.git
synced 2024-11-23 00:43:33 +01:00
Merge pull request #895 from Flare576/substring
Adds new substring function to str plugin
This commit is contained in:
commit
51879d022e
50
docs/commands/str.md
Normal file
50
docs/commands/str.md
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
# str
|
||||||
|
|
||||||
|
Consumes either a single value or a table and converts the provided data to a string and optionally applies a change.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
```shell
|
||||||
|
> shells
|
||||||
|
━━━┯━━━┯━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
# │ │ name │ path
|
||||||
|
───┼───┼────────────┼────────────────────────────────
|
||||||
|
0 │ X │ filesystem │ /home/TUX/stuff/expr/stuff
|
||||||
|
1 │ │ filesystem │ /
|
||||||
|
━━━┷━━━┷━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
> shells | str path --upcase
|
||||||
|
━━━┯━━━┯━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
# │ │ name │ path
|
||||||
|
───┼───┼────────────┼────────────────────────────────
|
||||||
|
0 │ X │ filesystem │ /HOME/TUX/STUFF/EXPR/STUFF
|
||||||
|
1 │ │ filesystem │ /
|
||||||
|
━━━┷━━━┷━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
> shells | str path --downcase
|
||||||
|
━━━┯━━━┯━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
# │ │ name │ path
|
||||||
|
───┼───┼────────────┼────────────────────────────────
|
||||||
|
0 │ X │ filesystem │ /home/tux/stuff/expr/stuff
|
||||||
|
1 │ │ filesystem │ /
|
||||||
|
━━━┷━━━┷━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
> shells | str # --substring "21, 99"
|
||||||
|
━━━┯━━━┯━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
# │ │ name │ path
|
||||||
|
───┼───┼────────────┼────────────────────────────────
|
||||||
|
0 │ X │ filesystem │ stuff
|
||||||
|
1 │ │ filesystem │
|
||||||
|
━━━┷━━━┷━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
> shells | str # --substring "6,"
|
||||||
|
━━━┯━━━┯━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
# │ │ name │ path
|
||||||
|
───┼───┼────────────┼────────────────────────────────
|
||||||
|
0 │ X │ filesystem │ TUX/stuff/expr/stuff
|
||||||
|
1 │ │ filesystem │
|
||||||
|
━━━┷━━━┷━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
|
||||||
|
> echo "1, 2, 3" | split-row "," | str --to-int | sum
|
||||||
|
━━━━━━━━━
|
||||||
|
<value>
|
||||||
|
─────────
|
||||||
|
6
|
||||||
|
━━━━━━━━━
|
||||||
|
```
|
@ -2,12 +2,14 @@ use nu::{
|
|||||||
did_you_mean, serve_plugin, tag_for_tagged_list, CallInfo, Plugin, Primitive, ReturnSuccess,
|
did_you_mean, serve_plugin, tag_for_tagged_list, CallInfo, Plugin, Primitive, ReturnSuccess,
|
||||||
ReturnValue, ShellError, Signature, SyntaxShape, Tagged, TaggedItem, Value,
|
ReturnValue, ShellError, Signature, SyntaxShape, Tagged, TaggedItem, Value,
|
||||||
};
|
};
|
||||||
|
use std::cmp;
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq)]
|
#[derive(Debug, Eq, PartialEq)]
|
||||||
enum Action {
|
enum Action {
|
||||||
Downcase,
|
Downcase,
|
||||||
Upcase,
|
Upcase,
|
||||||
ToInteger,
|
ToInteger,
|
||||||
|
Substring(usize, usize),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type ColumnPath = Vec<Tagged<String>>;
|
pub type ColumnPath = Vec<Tagged<String>>;
|
||||||
@ -33,6 +35,17 @@ impl Str {
|
|||||||
let applied = match self.action.as_ref() {
|
let applied = match self.action.as_ref() {
|
||||||
Some(Action::Downcase) => Value::string(input.to_ascii_lowercase()),
|
Some(Action::Downcase) => Value::string(input.to_ascii_lowercase()),
|
||||||
Some(Action::Upcase) => Value::string(input.to_ascii_uppercase()),
|
Some(Action::Upcase) => Value::string(input.to_ascii_uppercase()),
|
||||||
|
Some(Action::Substring(s, e)) => {
|
||||||
|
let end: usize = cmp::min(*e, input.len());
|
||||||
|
let start: usize = *s;
|
||||||
|
if start > input.len() - 1 {
|
||||||
|
Value::string("")
|
||||||
|
} else {
|
||||||
|
// Index operator isn't perfect:
|
||||||
|
// https://users.rust-lang.org/t/how-to-get-a-substring-of-a-string/1351
|
||||||
|
Value::string(&input[start..end])
|
||||||
|
}
|
||||||
|
}
|
||||||
Some(Action::ToInteger) => match input.trim() {
|
Some(Action::ToInteger) => match input.trim() {
|
||||||
other => match other.parse::<i64>() {
|
other => match other.parse::<i64>() {
|
||||||
Ok(v) => Value::int(v),
|
Ok(v) => Value::int(v),
|
||||||
@ -81,8 +94,27 @@ impl Str {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn for_substring(&mut self, s: String) {
|
||||||
|
let v: Vec<&str> = s.split(',').collect();
|
||||||
|
let start: usize = match v[0] {
|
||||||
|
"" => 0,
|
||||||
|
_ => v[0].trim().parse().unwrap(),
|
||||||
|
};
|
||||||
|
let end: usize = match v[1] {
|
||||||
|
"" => usize::max_value().clone(),
|
||||||
|
_ => v[1].trim().parse().unwrap(),
|
||||||
|
};
|
||||||
|
if start > end {
|
||||||
|
self.log_error("End must be greater than or equal to Start");
|
||||||
|
} else if self.permit() {
|
||||||
|
self.action = Some(Action::Substring(start, end));
|
||||||
|
} else {
|
||||||
|
self.log_error("can only apply one");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn usage() -> &'static str {
|
pub fn usage() -> &'static str {
|
||||||
"Usage: str field [--downcase|--upcase|--to-int]"
|
"Usage: str field [--downcase|--upcase|--to-int|--substring \"start,end\"]"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -167,6 +199,11 @@ impl Plugin for Str {
|
|||||||
.switch("downcase", "convert string to lowercase")
|
.switch("downcase", "convert string to lowercase")
|
||||||
.switch("upcase", "convert string to uppercase")
|
.switch("upcase", "convert string to uppercase")
|
||||||
.switch("to-int", "convert string to integer")
|
.switch("to-int", "convert string to integer")
|
||||||
|
.named(
|
||||||
|
"substring",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"convert string to portion of original, requires \"start,end\"",
|
||||||
|
)
|
||||||
.rest(SyntaxShape::ColumnPath, "the column(s) to convert")
|
.rest(SyntaxShape::ColumnPath, "the column(s) to convert")
|
||||||
.filter())
|
.filter())
|
||||||
}
|
}
|
||||||
@ -183,20 +220,34 @@ impl Plugin for Str {
|
|||||||
if args.has("to-int") {
|
if args.has("to-int") {
|
||||||
self.for_to_int();
|
self.for_to_int();
|
||||||
}
|
}
|
||||||
|
if args.has("substring") {
|
||||||
|
if let Some(start_end) = args.get("substring") {
|
||||||
|
match start_end {
|
||||||
|
Tagged {
|
||||||
|
item: Value::Primitive(Primitive::String(s)),
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
self.for_substring(s.to_string());
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err(ShellError::labeled_error(
|
||||||
|
"Unrecognized type in params",
|
||||||
|
start_end.type_name(),
|
||||||
|
&start_end.tag,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(possible_field) = args.nth(0) {
|
if let Some(possible_field) = args.nth(0) {
|
||||||
match possible_field {
|
match possible_field {
|
||||||
Tagged {
|
Tagged {
|
||||||
item: Value::Primitive(Primitive::String(s)),
|
item: Value::Primitive(Primitive::String(s)),
|
||||||
tag,
|
tag,
|
||||||
} => match self.action {
|
} => {
|
||||||
Some(Action::Downcase)
|
|
||||||
| Some(Action::Upcase)
|
|
||||||
| Some(Action::ToInteger)
|
|
||||||
| None => {
|
|
||||||
self.for_field(vec![s.clone().tagged(tag)]);
|
self.for_field(vec![s.clone().tagged(tag)]);
|
||||||
}
|
}
|
||||||
},
|
|
||||||
table @ Tagged {
|
table @ Tagged {
|
||||||
item: Value::Table(_),
|
item: Value::Table(_),
|
||||||
..
|
..
|
||||||
@ -212,7 +263,6 @@ impl Plugin for Str {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for param in args.positional_iter() {
|
for param in args.positional_iter() {
|
||||||
match param {
|
match param {
|
||||||
Tagged {
|
Tagged {
|
||||||
@ -267,6 +317,14 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn with_named_parameter(&mut self, name: &str, value: &str) -> &mut Self {
|
||||||
|
self.flags.insert(
|
||||||
|
name.to_string(),
|
||||||
|
Value::string(value).tagged(Tag::unknown()),
|
||||||
|
);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
fn with_long_flag(&mut self, name: &str) -> &mut Self {
|
fn with_long_flag(&mut self, name: &str) -> &mut Self {
|
||||||
self.flags.insert(
|
self.flags.insert(
|
||||||
name.to_string(),
|
name.to_string(),
|
||||||
@ -374,6 +432,7 @@ mod tests {
|
|||||||
.with_long_flag("upcase")
|
.with_long_flag("upcase")
|
||||||
.with_long_flag("downcase")
|
.with_long_flag("downcase")
|
||||||
.with_long_flag("to-int")
|
.with_long_flag("to-int")
|
||||||
|
.with_long_flag("substring")
|
||||||
.create(),
|
.create(),
|
||||||
)
|
)
|
||||||
.is_err());
|
.is_err());
|
||||||
@ -544,4 +603,141 @@ mod tests {
|
|||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn str_plugin_applies_substring_without_field() {
|
||||||
|
let mut plugin = Str::new();
|
||||||
|
|
||||||
|
assert!(plugin
|
||||||
|
.begin_filter(
|
||||||
|
CallStub::new()
|
||||||
|
.with_named_parameter("substring", "0,1")
|
||||||
|
.create()
|
||||||
|
)
|
||||||
|
.is_ok());
|
||||||
|
|
||||||
|
let subject = unstructured_sample_record("0123456789");
|
||||||
|
let output = plugin.filter(subject).unwrap();
|
||||||
|
|
||||||
|
match output[0].as_ref().unwrap() {
|
||||||
|
ReturnSuccess::Value(Tagged {
|
||||||
|
item: Value::Primitive(Primitive::String(s)),
|
||||||
|
..
|
||||||
|
}) => assert_eq!(*s, String::from("0")),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn str_plugin_applies_substring_exceeding_string_length() {
|
||||||
|
let mut plugin = Str::new();
|
||||||
|
|
||||||
|
assert!(plugin
|
||||||
|
.begin_filter(
|
||||||
|
CallStub::new()
|
||||||
|
.with_named_parameter("substring", "0,11")
|
||||||
|
.create()
|
||||||
|
)
|
||||||
|
.is_ok());
|
||||||
|
|
||||||
|
let subject = unstructured_sample_record("0123456789");
|
||||||
|
let output = plugin.filter(subject).unwrap();
|
||||||
|
|
||||||
|
match output[0].as_ref().unwrap() {
|
||||||
|
ReturnSuccess::Value(Tagged {
|
||||||
|
item: Value::Primitive(Primitive::String(s)),
|
||||||
|
..
|
||||||
|
}) => assert_eq!(*s, String::from("0123456789")),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn str_plugin_applies_substring_returns_blank_if_start_exceeds_length() {
|
||||||
|
let mut plugin = Str::new();
|
||||||
|
|
||||||
|
assert!(plugin
|
||||||
|
.begin_filter(
|
||||||
|
CallStub::new()
|
||||||
|
.with_named_parameter("substring", "20,30")
|
||||||
|
.create()
|
||||||
|
)
|
||||||
|
.is_ok());
|
||||||
|
|
||||||
|
let subject = unstructured_sample_record("0123456789");
|
||||||
|
let output = plugin.filter(subject).unwrap();
|
||||||
|
|
||||||
|
match output[0].as_ref().unwrap() {
|
||||||
|
ReturnSuccess::Value(Tagged {
|
||||||
|
item: Value::Primitive(Primitive::String(s)),
|
||||||
|
..
|
||||||
|
}) => assert_eq!(*s, String::from("")),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn str_plugin_applies_substring_treats_blank_start_as_zero() {
|
||||||
|
let mut plugin = Str::new();
|
||||||
|
|
||||||
|
assert!(plugin
|
||||||
|
.begin_filter(
|
||||||
|
CallStub::new()
|
||||||
|
.with_named_parameter("substring", ",5")
|
||||||
|
.create()
|
||||||
|
)
|
||||||
|
.is_ok());
|
||||||
|
|
||||||
|
let subject = unstructured_sample_record("0123456789");
|
||||||
|
let output = plugin.filter(subject).unwrap();
|
||||||
|
|
||||||
|
match output[0].as_ref().unwrap() {
|
||||||
|
ReturnSuccess::Value(Tagged {
|
||||||
|
item: Value::Primitive(Primitive::String(s)),
|
||||||
|
..
|
||||||
|
}) => assert_eq!(*s, String::from("01234")),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn str_plugin_applies_substring_treats_blank_end_as_length() {
|
||||||
|
let mut plugin = Str::new();
|
||||||
|
|
||||||
|
assert!(plugin
|
||||||
|
.begin_filter(
|
||||||
|
CallStub::new()
|
||||||
|
.with_named_parameter("substring", "2,")
|
||||||
|
.create()
|
||||||
|
)
|
||||||
|
.is_ok());
|
||||||
|
|
||||||
|
let subject = unstructured_sample_record("0123456789");
|
||||||
|
let output = plugin.filter(subject).unwrap();
|
||||||
|
|
||||||
|
match output[0].as_ref().unwrap() {
|
||||||
|
ReturnSuccess::Value(Tagged {
|
||||||
|
item: Value::Primitive(Primitive::String(s)),
|
||||||
|
..
|
||||||
|
}) => assert_eq!(*s, String::from("23456789")),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn str_plugin_applies_substring_returns_error_if_start_exceeds_end() {
|
||||||
|
let mut plugin = Str::new();
|
||||||
|
|
||||||
|
assert!(plugin
|
||||||
|
.begin_filter(
|
||||||
|
CallStub::new()
|
||||||
|
.with_named_parameter("substring", "3,1")
|
||||||
|
.create()
|
||||||
|
)
|
||||||
|
.is_err());
|
||||||
|
assert_eq!(
|
||||||
|
plugin.error,
|
||||||
|
Some("End must be greater than or equal to Start".to_string())
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user