mirror of
https://github.com/nushell/nushell
synced 2024-12-25 20:43:09 +00:00
Merge pull request #1016 from andrasio/str
replace and find-replace str plugin additions.
This commit is contained in:
commit
186b75a848
3 changed files with 372 additions and 37 deletions
|
@ -263,6 +263,10 @@ impl Value {
|
|||
Ok(ColumnPath::new(out).tagged(&self.tag))
|
||||
}
|
||||
|
||||
UntaggedValue::Primitive(Primitive::String(s)) => {
|
||||
Ok(ColumnPath::new(vec![PathMember::string(s, &self.tag.span)]).tagged(&self.tag))
|
||||
}
|
||||
|
||||
UntaggedValue::Primitive(Primitive::ColumnPath(path)) => {
|
||||
Ok(path.clone().tagged(self.tag.clone()))
|
||||
}
|
||||
|
@ -299,6 +303,9 @@ impl Value {
|
|||
UntaggedValue::Primitive(Primitive::Int(x)) => Ok(format!("{}", x)),
|
||||
UntaggedValue::Primitive(Primitive::Bytes(x)) => Ok(format!("{}", x)),
|
||||
UntaggedValue::Primitive(Primitive::Path(x)) => Ok(format!("{}", x.display())),
|
||||
UntaggedValue::Primitive(Primitive::ColumnPath(path)) => {
|
||||
Ok(path.iter().map(|member| member.display()).join("."))
|
||||
}
|
||||
// TODO: this should definitely be more general with better errors
|
||||
other => Err(ShellError::labeled_error(
|
||||
"Expected string",
|
||||
|
|
|
@ -4,6 +4,7 @@ use nu::{
|
|||
};
|
||||
use nu_source::{span_for_spanned_list, Tagged};
|
||||
|
||||
use regex::Regex;
|
||||
use std::cmp;
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
|
@ -12,11 +13,17 @@ enum Action {
|
|||
Upcase,
|
||||
ToInteger,
|
||||
Substring(usize, usize),
|
||||
Replace(ReplaceAction),
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
enum ReplaceAction {
|
||||
Direct(String),
|
||||
FindAndReplace(String, String),
|
||||
}
|
||||
|
||||
struct Str {
|
||||
field: Option<Tagged<ColumnPath>>,
|
||||
params: Option<Vec<String>>,
|
||||
error: Option<String>,
|
||||
action: Option<Action>,
|
||||
}
|
||||
|
@ -25,7 +32,6 @@ impl Str {
|
|||
fn new() -> Str {
|
||||
Str {
|
||||
field: None,
|
||||
params: Some(Vec::<String>::new()),
|
||||
error: None,
|
||||
action: None,
|
||||
}
|
||||
|
@ -50,6 +56,19 @@ impl Str {
|
|||
)
|
||||
}
|
||||
}
|
||||
Some(Action::Replace(mode)) => match mode {
|
||||
ReplaceAction::Direct(replacement) => UntaggedValue::string(replacement.as_str()),
|
||||
ReplaceAction::FindAndReplace(find, replacement) => {
|
||||
let regex = Regex::new(find.as_str());
|
||||
|
||||
match regex {
|
||||
Ok(re) => UntaggedValue::string(
|
||||
re.replace(input, replacement.as_str()).to_owned(),
|
||||
),
|
||||
Err(_) => UntaggedValue::string(input),
|
||||
}
|
||||
}
|
||||
},
|
||||
Some(Action::ToInteger) => match input.trim() {
|
||||
other => match other.parse::<i64>() {
|
||||
Ok(v) => UntaggedValue::int(v),
|
||||
|
@ -117,8 +136,16 @@ impl Str {
|
|||
}
|
||||
}
|
||||
|
||||
fn for_replace(&mut self, mode: ReplaceAction) {
|
||||
if self.permit() {
|
||||
self.action = Some(Action::Replace(mode));
|
||||
} else {
|
||||
self.log_error("can only apply one");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn usage() -> &'static str {
|
||||
"Usage: str field [--downcase|--upcase|--to-int|--substring \"start,end\"]"
|
||||
"Usage: str field [--downcase|--upcase|--to-int|--substring \"start,end\"|--replace|--find-replace [pattern replacement]]]"
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -186,6 +213,12 @@ impl Plugin for Str {
|
|||
.switch("downcase", "convert string to lowercase")
|
||||
.switch("upcase", "convert string to uppercase")
|
||||
.switch("to-int", "convert string to integer")
|
||||
.named("replace", SyntaxShape::String, "replaces the string")
|
||||
.named(
|
||||
"find-replace",
|
||||
SyntaxShape::Any,
|
||||
"finds and replaces [pattern replacement]",
|
||||
)
|
||||
.named(
|
||||
"substring",
|
||||
SyntaxShape::String,
|
||||
|
@ -226,21 +259,33 @@ impl Plugin for Str {
|
|||
}
|
||||
}
|
||||
}
|
||||
if args.has("replace") {
|
||||
if let Some(Value {
|
||||
value: UntaggedValue::Primitive(Primitive::String(replacement)),
|
||||
..
|
||||
}) = args.get("replace")
|
||||
{
|
||||
self.for_replace(ReplaceAction::Direct(replacement.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
if args.has("find-replace") {
|
||||
if let Some(Value {
|
||||
value: UntaggedValue::Table(arguments),
|
||||
..
|
||||
}) = args.get("find-replace")
|
||||
{
|
||||
self.for_replace(ReplaceAction::FindAndReplace(
|
||||
arguments.get(0).unwrap().as_string()?,
|
||||
arguments.get(1).unwrap().as_string()?,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(possible_field) = args.nth(0) {
|
||||
let possible_field = possible_field.as_column_path()?;
|
||||
|
||||
self.for_field(possible_field);
|
||||
}
|
||||
for param in args.positional_iter() {
|
||||
match param {
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::String(s)),
|
||||
..
|
||||
} => self.params.as_mut().unwrap().push(String::from(s)),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
match &self.error {
|
||||
Some(reason) => {
|
||||
|
@ -265,15 +310,32 @@ fn main() {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{Action, Str};
|
||||
use super::{Action, ReplaceAction, Str};
|
||||
use indexmap::IndexMap;
|
||||
use nu::{
|
||||
CallInfo, EvaluatedArgs, Plugin, Primitive, ReturnSuccess, TaggedDictBuilder,
|
||||
UnspannedPathMember, UntaggedValue, Value,
|
||||
UntaggedValue, Value,
|
||||
};
|
||||
use nu_source::Tag;
|
||||
use num_bigint::BigInt;
|
||||
|
||||
fn string(input: impl Into<String>) -> Value {
|
||||
UntaggedValue::string(input.into()).into_untagged_value()
|
||||
}
|
||||
|
||||
fn table(list: &Vec<Value>) -> Value {
|
||||
UntaggedValue::table(list).into_untagged_value()
|
||||
}
|
||||
|
||||
fn column_path(paths: &Vec<Value>) -> Value {
|
||||
UntaggedValue::Primitive(Primitive::ColumnPath(
|
||||
table(&paths.iter().cloned().collect())
|
||||
.as_column_path()
|
||||
.unwrap()
|
||||
.item,
|
||||
))
|
||||
.into_untagged_value()
|
||||
}
|
||||
struct CallStub {
|
||||
positionals: Vec<Value>,
|
||||
flags: IndexMap<String, Value>,
|
||||
|
@ -287,11 +349,8 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
fn with_named_parameter(&mut self, name: &str, value: &str) -> &mut Self {
|
||||
self.flags.insert(
|
||||
name.to_string(),
|
||||
UntaggedValue::string(value).into_value(Tag::unknown()),
|
||||
);
|
||||
fn with_named_parameter(&mut self, name: &str, value: Value) -> &mut Self {
|
||||
self.flags.insert(name.to_string(), value);
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -309,8 +368,7 @@ mod tests {
|
|||
.map(|s| UntaggedValue::string(s.to_string()).into_value(Tag::unknown()))
|
||||
.collect();
|
||||
|
||||
self.positionals
|
||||
.push(UntaggedValue::Table(fields).into_value(Tag::unknown()));
|
||||
self.positionals.push(column_path(&fields));
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -338,7 +396,14 @@ mod tests {
|
|||
|
||||
let configured = plugin.config().unwrap();
|
||||
|
||||
for action_flag in &["downcase", "upcase", "to-int"] {
|
||||
for action_flag in &[
|
||||
"downcase",
|
||||
"upcase",
|
||||
"to-int",
|
||||
"substring",
|
||||
"replace",
|
||||
"find-replace",
|
||||
] {
|
||||
assert!(configured.named.get(*action_flag).is_some());
|
||||
}
|
||||
}
|
||||
|
@ -372,6 +437,55 @@ mod tests {
|
|||
.is_ok());
|
||||
assert_eq!(plugin.action.unwrap(), Action::ToInteger);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn str_plugin_accepts_replace() {
|
||||
let mut plugin = Str::new();
|
||||
|
||||
let argument = String::from("replace_text");
|
||||
|
||||
assert!(plugin
|
||||
.begin_filter(
|
||||
CallStub::new()
|
||||
.with_named_parameter("replace", string(&argument))
|
||||
.create()
|
||||
)
|
||||
.is_ok());
|
||||
|
||||
match plugin.action {
|
||||
Some(Action::Replace(ReplaceAction::Direct(replace_with))) => {
|
||||
assert_eq!(replace_with, argument)
|
||||
}
|
||||
Some(_) | None => panic!("Din't accept."),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn str_plugin_accepts_find_replace() {
|
||||
let mut plugin = Str::new();
|
||||
|
||||
let search_argument = String::from("kittens");
|
||||
let replace_argument = String::from("jotandrehuda");
|
||||
|
||||
assert!(plugin
|
||||
.begin_filter(
|
||||
CallStub::new()
|
||||
.with_named_parameter(
|
||||
"find-replace",
|
||||
table(&vec![string(&search_argument), string(&replace_argument)])
|
||||
)
|
||||
.create()
|
||||
)
|
||||
.is_ok());
|
||||
|
||||
match plugin.action {
|
||||
Some(Action::Replace(ReplaceAction::FindAndReplace(find_with, replace_with))) => {
|
||||
assert_eq!(find_with, search_argument);
|
||||
assert_eq!(replace_with, replace_argument);
|
||||
}
|
||||
Some(_) | None => panic!("Din't accept."),
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn str_plugin_accepts_field() {
|
||||
let mut plugin = Str::new();
|
||||
|
@ -384,14 +498,13 @@ mod tests {
|
|||
)
|
||||
.is_ok());
|
||||
|
||||
let actual = &*plugin.field.unwrap();
|
||||
let actual = UntaggedValue::Primitive(Primitive::ColumnPath(actual.clone()));
|
||||
let actual = actual.into_value(Tag::unknown());
|
||||
|
||||
assert_eq!(
|
||||
plugin
|
||||
.field
|
||||
.map(|f| f.iter().cloned().map(|f| f.unspanned).collect()),
|
||||
Some(vec![
|
||||
UnspannedPathMember::String("package".to_string()),
|
||||
UnspannedPathMember::String("description".to_string())
|
||||
])
|
||||
actual,
|
||||
column_path(&vec![string("package"), string("description")])
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -442,6 +555,30 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn str_replace() {
|
||||
let mut strutils = Str::new();
|
||||
strutils.for_replace(ReplaceAction::Direct("robalino".to_string()));
|
||||
|
||||
assert_eq!(
|
||||
strutils.apply("andres").unwrap(),
|
||||
UntaggedValue::string("robalino")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn str_find_replace() {
|
||||
let mut strutils = Str::new();
|
||||
strutils.for_replace(ReplaceAction::FindAndReplace(
|
||||
"kittens".to_string(),
|
||||
"jotandrehuda".to_string(),
|
||||
));
|
||||
assert_eq!(
|
||||
strutils.apply("wykittens").unwrap(),
|
||||
UntaggedValue::string("wyjotandrehuda")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn str_plugin_applies_upcase_with_field() {
|
||||
let mut plugin = Str::new();
|
||||
|
@ -593,7 +730,7 @@ mod tests {
|
|||
assert!(plugin
|
||||
.begin_filter(
|
||||
CallStub::new()
|
||||
.with_named_parameter("substring", "0,1")
|
||||
.with_named_parameter("substring", string("0,1"))
|
||||
.create()
|
||||
)
|
||||
.is_ok());
|
||||
|
@ -617,7 +754,7 @@ mod tests {
|
|||
assert!(plugin
|
||||
.begin_filter(
|
||||
CallStub::new()
|
||||
.with_named_parameter("substring", "0,11")
|
||||
.with_named_parameter("substring", string("0,11"))
|
||||
.create()
|
||||
)
|
||||
.is_ok());
|
||||
|
@ -641,7 +778,7 @@ mod tests {
|
|||
assert!(plugin
|
||||
.begin_filter(
|
||||
CallStub::new()
|
||||
.with_named_parameter("substring", "20,30")
|
||||
.with_named_parameter("substring", string("20,30"))
|
||||
.create()
|
||||
)
|
||||
.is_ok());
|
||||
|
@ -665,7 +802,7 @@ mod tests {
|
|||
assert!(plugin
|
||||
.begin_filter(
|
||||
CallStub::new()
|
||||
.with_named_parameter("substring", ",5")
|
||||
.with_named_parameter("substring", string(",5"))
|
||||
.create()
|
||||
)
|
||||
.is_ok());
|
||||
|
@ -689,7 +826,7 @@ mod tests {
|
|||
assert!(plugin
|
||||
.begin_filter(
|
||||
CallStub::new()
|
||||
.with_named_parameter("substring", "2,")
|
||||
.with_named_parameter("substring", string("2,"))
|
||||
.create()
|
||||
)
|
||||
.is_ok());
|
||||
|
@ -713,7 +850,7 @@ mod tests {
|
|||
assert!(plugin
|
||||
.begin_filter(
|
||||
CallStub::new()
|
||||
.with_named_parameter("substring", "3,1")
|
||||
.with_named_parameter("substring", string("3,1"))
|
||||
.create()
|
||||
)
|
||||
.is_err());
|
||||
|
@ -722,4 +859,120 @@ mod tests {
|
|||
Some("End must be greater than or equal to Start".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn str_plugin_applies_replace_with_field() {
|
||||
let mut plugin = Str::new();
|
||||
|
||||
assert!(plugin
|
||||
.begin_filter(
|
||||
CallStub::new()
|
||||
.with_parameter("rustconf")
|
||||
.with_named_parameter("replace", string("22nd August 2019"))
|
||||
.create()
|
||||
)
|
||||
.is_ok());
|
||||
|
||||
let subject = structured_sample_record("rustconf", "1st January 1970");
|
||||
let output = plugin.filter(subject).unwrap();
|
||||
|
||||
match output[0].as_ref().unwrap() {
|
||||
ReturnSuccess::Value(Value {
|
||||
value: UntaggedValue::Row(o),
|
||||
..
|
||||
}) => assert_eq!(
|
||||
*o.get_data(&String::from("rustconf")).borrow(),
|
||||
Value {
|
||||
value: UntaggedValue::string(String::from("22nd August 2019")),
|
||||
tag: Tag::unknown()
|
||||
}
|
||||
),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn str_plugin_applies_replace_without_field() {
|
||||
let mut plugin = Str::new();
|
||||
|
||||
assert!(plugin
|
||||
.begin_filter(
|
||||
CallStub::new()
|
||||
.with_named_parameter("replace", string("22nd August 2019"))
|
||||
.create()
|
||||
)
|
||||
.is_ok());
|
||||
|
||||
let subject = unstructured_sample_record("1st January 1970");
|
||||
let output = plugin.filter(subject).unwrap();
|
||||
|
||||
match output[0].as_ref().unwrap() {
|
||||
ReturnSuccess::Value(Value {
|
||||
value: UntaggedValue::Primitive(Primitive::String(s)),
|
||||
..
|
||||
}) => assert_eq!(*s, String::from("22nd August 2019")),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn str_plugin_applies_find_replace_with_field() {
|
||||
let mut plugin = Str::new();
|
||||
|
||||
assert!(plugin
|
||||
.begin_filter(
|
||||
CallStub::new()
|
||||
.with_parameter("staff")
|
||||
.with_named_parameter(
|
||||
"find-replace",
|
||||
table(&vec![string("kittens"), string("jotandrehuda")])
|
||||
)
|
||||
.create()
|
||||
)
|
||||
.is_ok());
|
||||
|
||||
let subject = structured_sample_record("staff", "wykittens");
|
||||
let output = plugin.filter(subject).unwrap();
|
||||
|
||||
match output[0].as_ref().unwrap() {
|
||||
ReturnSuccess::Value(Value {
|
||||
value: UntaggedValue::Row(o),
|
||||
..
|
||||
}) => assert_eq!(
|
||||
*o.get_data(&String::from("staff")).borrow(),
|
||||
Value {
|
||||
value: UntaggedValue::string(String::from("wyjotandrehuda")),
|
||||
tag: Tag::unknown()
|
||||
}
|
||||
),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn str_plugin_applies_find_replace_without_field() {
|
||||
let mut plugin = Str::new();
|
||||
|
||||
assert!(plugin
|
||||
.begin_filter(
|
||||
CallStub::new()
|
||||
.with_named_parameter(
|
||||
"find-replace",
|
||||
table(&vec![string("kittens"), string("jotandrehuda")])
|
||||
)
|
||||
.create()
|
||||
)
|
||||
.is_ok());
|
||||
|
||||
let subject = unstructured_sample_record("wykittens");
|
||||
let output = plugin.filter(subject).unwrap();
|
||||
|
||||
match output[0].as_ref().unwrap() {
|
||||
ReturnSuccess::Value(Value {
|
||||
value: UntaggedValue::Primitive(Primitive::String(s)),
|
||||
..
|
||||
}) => assert_eq!(*s, String::from("wyjotandrehuda")),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ fn can_only_apply_one() {
|
|||
"open caco3_plastics.csv | first 1 | str origin --downcase --upcase"
|
||||
);
|
||||
|
||||
assert!(actual.contains("Usage: str field [--downcase|--upcase|--to-int"));
|
||||
assert!(actual.contains(r#"--downcase|--upcase|--to-int|--substring "start,end"|--replace|--find-replace [pattern replacement]]"#));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -90,3 +90,78 @@ fn converts_to_int() {
|
|||
|
||||
assert_eq!(actual, "2509000000");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn replaces() {
|
||||
Playground::setup("plugin_str_test_4", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![FileWithContent(
|
||||
"sample.toml",
|
||||
r#"
|
||||
[package]
|
||||
name = "nushell"
|
||||
"#,
|
||||
)]);
|
||||
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(), h::pipeline(
|
||||
r#"
|
||||
open sample.toml
|
||||
| str package.name --replace wykittenshell
|
||||
| get package.name
|
||||
| echo $it
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual, "wykittenshell");
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_and_replaces() {
|
||||
Playground::setup("plugin_str_test_5", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![FileWithContent(
|
||||
"sample.toml",
|
||||
r#"
|
||||
[fortune.teller]
|
||||
phone = "1-800-KATZ"
|
||||
"#,
|
||||
)]);
|
||||
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(), h::pipeline(
|
||||
r#"
|
||||
open sample.toml
|
||||
| str fortune.teller.phone --find-replace [KATZ 5289]
|
||||
| get fortune.teller.phone
|
||||
| echo $it
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual, "1-800-5289");
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_and_replaces_without_passing_field() {
|
||||
Playground::setup("plugin_str_test_6", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![FileWithContent(
|
||||
"sample.toml",
|
||||
r#"
|
||||
[fortune.teller]
|
||||
phone = "1-800-KATZ"
|
||||
"#,
|
||||
)]);
|
||||
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(), h::pipeline(
|
||||
r#"
|
||||
open sample.toml
|
||||
| get fortune.teller.phone
|
||||
| str --find-replace [KATZ 5289]
|
||||
| echo $it
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual, "1-800-5289");
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue