mirror of
https://github.com/nushell/nushell.git
synced 2024-12-23 23:49:44 +01:00
Substring option for str plugin
Adds new substr function to str plugin with tests and documentation Function takes a start/end location as a string in the form "##,##", both sides of comma are optional, and behaves like Rust's own index operator [##..##].
This commit is contained in:
parent
9d34ec9153
commit
cd058db046
8
Cargo.lock
generated
8
Cargo.lock
generated
@ -1540,7 +1540,7 @@ dependencies = [
|
||||
"regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"roxmltree 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rusqlite 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustyline 5.0.3 (git+https://github.com/kkawakam/rustyline.git)",
|
||||
"rustyline 5.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde-hjson 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@ -2077,8 +2077,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustyline"
|
||||
version = "5.0.3"
|
||||
source = "git+https://github.com/kkawakam/rustyline.git#449c811998f630102bb2d9fb0b59b890d9eabac5"
|
||||
version = "5.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@ -3056,7 +3056,7 @@ dependencies = [
|
||||
"checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783"
|
||||
"checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda"
|
||||
"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
|
||||
"checksum rustyline 5.0.3 (git+https://github.com/kkawakam/rustyline.git)" = "<none>"
|
||||
"checksum rustyline 5.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "e9d8eb9912bc492db051324d36f5cea56984fc2afeaa5c6fa84e0b0e3cde550f"
|
||||
"checksum ryu 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "19d2271fa48eaf61e53cc88b4ad9adcbafa2d512c531e7fadb6dc11a4d3656c5"
|
||||
"checksum safemem 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d2b08423011dae9a5ca23f07cf57dac3857f5c885d352b76f6d95f4aea9434d0"
|
||||
"checksum same-file 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "585e8ddcedc187886a30fa705c47985c3fa88d06624095856b36ca0b82ff4421"
|
||||
|
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 # --to-int
|
||||
━━━┯━━━┯━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
# │ │ 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 │
|
||||
━━━┷━━━┷━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
```
|
@ -2,12 +2,14 @@ use nu::{
|
||||
serve_plugin, CallInfo, Plugin, Primitive, ReturnSuccess, ReturnValue, ShellError, Signature,
|
||||
SyntaxShape, Tagged, TaggedItem, Value,
|
||||
};
|
||||
use std::cmp;
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
enum Action {
|
||||
Downcase,
|
||||
Upcase,
|
||||
ToInteger,
|
||||
Substring(String),
|
||||
}
|
||||
|
||||
pub type ColumnPath = Vec<Tagged<String>>;
|
||||
@ -33,6 +35,26 @@ impl Str {
|
||||
let applied = match self.action.as_ref() {
|
||||
Some(Action::Downcase) => Value::string(input.to_ascii_lowercase()),
|
||||
Some(Action::Upcase) => Value::string(input.to_ascii_uppercase()),
|
||||
Some(Action::Substring(s)) => {
|
||||
// Index operator isn't perfect: https://users.rust-lang.org/t/how-to-get-a-substring-of-a-string/1351
|
||||
let no_spaces: String = s.chars().filter(|c| !c.is_whitespace()).collect();
|
||||
let v: Vec<&str> = no_spaces.split(',').collect();
|
||||
let start: usize = match v[0] {
|
||||
"" => 0,
|
||||
_ => v[0].parse().unwrap(),
|
||||
};
|
||||
let end: usize = match v[1] {
|
||||
"" => input.len(),
|
||||
_ => cmp::min(v[1].parse().unwrap(), input.len()),
|
||||
};
|
||||
if start > input.len() - 1 {
|
||||
Value::string("")
|
||||
} else if start > end {
|
||||
Value::string(input)
|
||||
} else {
|
||||
Value::string(&input[start..end])
|
||||
}
|
||||
}
|
||||
Some(Action::ToInteger) => match input.trim() {
|
||||
other => match other.parse::<i64>() {
|
||||
Ok(v) => Value::int(v),
|
||||
@ -81,8 +103,16 @@ impl Str {
|
||||
}
|
||||
}
|
||||
|
||||
fn for_substring(&mut self, start_end: String) {
|
||||
if self.permit() {
|
||||
self.action = Some(Action::Substring(start_end));
|
||||
} else {
|
||||
self.log_error("can only apply one");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn usage() -> &'static str {
|
||||
"Usage: str field [--downcase|--upcase|--to-int]"
|
||||
"Usage: str field [--downcase|--upcase|--to-int|--substring \"start,end\"]"
|
||||
}
|
||||
}
|
||||
|
||||
@ -132,6 +162,11 @@ impl Plugin for Str {
|
||||
.switch("downcase", "convert string to lowercase")
|
||||
.switch("upcase", "convert string to uppercase")
|
||||
.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")
|
||||
.filter())
|
||||
}
|
||||
@ -148,20 +183,34 @@ impl Plugin for Str {
|
||||
if args.has("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) {
|
||||
match possible_field {
|
||||
Tagged {
|
||||
item: Value::Primitive(Primitive::String(s)),
|
||||
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 {
|
||||
item: Value::Table(_),
|
||||
..
|
||||
@ -177,7 +226,6 @@ impl Plugin for Str {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for param in args.positional_iter() {
|
||||
match param {
|
||||
Tagged {
|
||||
@ -232,6 +280,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 {
|
||||
self.flags.insert(
|
||||
name.to_string(),
|
||||
@ -339,6 +395,7 @@ mod tests {
|
||||
.with_long_flag("upcase")
|
||||
.with_long_flag("downcase")
|
||||
.with_long_flag("to-int")
|
||||
.with_long_flag("substring")
|
||||
.create(),
|
||||
)
|
||||
.is_err());
|
||||
@ -509,4 +566,148 @@ 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_string_if_start_exceeds_end() {
|
||||
let mut plugin = Str::new();
|
||||
|
||||
assert!(plugin
|
||||
.begin_filter(
|
||||
CallStub::new()
|
||||
.with_named_parameter("substring", "3,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("0123456789")),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user