Add and edit functionality

This commit is contained in:
Jonathan Turner 2019-07-22 15:52:57 +12:00
parent 12a785f2a2
commit ec7d49b0d2
8 changed files with 270 additions and 32 deletions

View file

@ -90,6 +90,14 @@ path = "src/lib.rs"
name = "nu_plugin_inc"
path = "src/plugins/inc.rs"
[[bin]]
name = "nu_plugin_add"
path = "src/plugins/add.rs"
[[bin]]
name = "nu_plugin_edit"
path = "src/plugins/edit.rs"
[[bin]]
name = "nu_plugin_skip"
path = "src/plugins/skip.rs"

View file

@ -136,6 +136,8 @@ Nu adheres closely to a set of goals that make up its design philosophy. As feat
| sort-by ...columns | Sort by the given columns |
| where condition | Filter table to match the condition |
| inc (field) | Increment a value or version. Optional use the field of a table |
| add field value | Add a new field to the table |
| edit field value | Edit an existing field to have a new value |
| skip amount | Skip a number of rows |
| first amount | Show only the first number of rows |
| to-array | Collapse rows into a single list |

View file

@ -1,6 +1,5 @@
use crate::object::{Primitive, Value};
use crate::prelude::*;
use log::trace;
pub fn value_to_json_value(v: &Value) -> serde_json::Value {
match v {

View file

@ -36,36 +36,17 @@ pub fn to_toml(args: CommandArgs) -> Result<OutputStream, ShellError> {
Ok(out
.values
.map(move |a| {
match toml::to_string(&value_to_toml_value(&a)) {
Ok(val) => {
return ReturnSuccess::value(
Value::Primitive(Primitive::String(val)).spanned(name_span),
)
}
Err(err) => Err(ShellError::type_error(
"serializable to toml",
format!("{:?} - {:?}", a.type_name(), err).spanned(name_span),
)), // toml::Value::String(String) => {
// return ReturnSuccess::value(
// Value::Primitive(Primitive::String(x)).spanned(name_span),
// )
// }
// toml::Value::Integer(i64) => "Integer",
// toml::Value::Float(f64) => "Decimal",
// toml::Value::Boolean(bool) => "Boolean",
// toml::Value::Datetime(Datetime) => "Date",
// toml::Value::Array(Array) => "Array",
// toml::Value::Table(Table) => "Table",
.map(move |a| match toml::to_string(&value_to_toml_value(&a)) {
Ok(val) => {
return ReturnSuccess::value(
Value::Primitive(Primitive::String(val)).spanned(name_span),
)
}
// return Err(ShellError::type_error("String", ty.spanned(name_span)));
// Err(_) => Err(ShellError::maybe_labeled_error(
// "Can not convert to TOML string",
// "can not convert piped data to TOML string",
// name_span,
// )),
Err(err) => Err(ShellError::type_error(
"Can not convert to a TOML string",
format!("{:?} - {:?}", a.type_name(), err).spanned(name_span),
)),
})
.to_output_stream())
}

View file

@ -362,6 +362,56 @@ impl Value {
})
}
pub fn insert_data_at_path(
&'a self,
span: Span,
path: &str,
new_value: Value,
) -> Option<Spanned<Value>> {
let mut new_obj = self.clone();
let split_path: Vec<_> = path.split(".").collect();
if let Value::Object(ref mut o) = new_obj {
let mut current = o;
for idx in 0..split_path.len() - 1 {
match current.entries.get_mut(split_path[idx]) {
Some(next) => {
if idx == (split_path.len() - 2) {
match &mut next.item {
Value::Object(o) => {
o.entries.insert(
split_path[idx + 1].to_string(),
Spanned {
item: new_value,
span,
},
);
}
_ => {}
}
return Some(Spanned {
item: new_obj,
span,
});
} else {
match next.item {
Value::Object(ref mut o) => {
current = o;
}
_ => return None,
}
}
}
_ => return None,
}
}
}
None
}
pub fn replace_data_at_path(
&'a self,
span: Span,

89
src/plugins/add.rs Normal file
View file

@ -0,0 +1,89 @@
use indexmap::IndexMap;
use nu::{
serve_plugin, CallInfo, CommandConfig, Plugin, PositionalType, Primitive, ReturnSuccess,
ReturnValue, ShellError, Spanned, Value,
};
struct Add {
field: Option<String>,
value: Option<Value>,
}
impl Add {
fn new() -> Add {
Add {
field: None,
value: None,
}
}
fn add(&self, value: Spanned<Value>) -> Result<Spanned<Value>, ShellError> {
match (value.item, self.value.clone()) {
(obj @ Value::Object(_), Some(v)) => match &self.field {
Some(f) => match obj.insert_data_at_path(value.span, &f, v) {
Some(v) => return Ok(v),
None => {
return Err(ShellError::string(
"add could not find place to insert field",
))
}
},
None => Err(ShellError::string(
"add needs a field when adding a value to an object",
)),
},
x => Err(ShellError::string(format!(
"Unrecognized type in stream: {:?}",
x
))),
}
}
}
impl Plugin for Add {
fn config(&mut self) -> Result<CommandConfig, ShellError> {
Ok(CommandConfig {
name: "add".to_string(),
positional: vec![
PositionalType::mandatory_any("Field"),
PositionalType::mandatory_any("Value"),
],
is_filter: true,
is_sink: false,
named: IndexMap::new(),
rest_positional: true,
})
}
fn begin_filter(&mut self, call_info: CallInfo) -> Result<(), ShellError> {
if let Some(args) = call_info.args.positional {
match &args[0] {
Spanned {
item: Value::Primitive(Primitive::String(s)),
..
} => {
self.field = Some(s.clone());
}
_ => {
return Err(ShellError::string(format!(
"Unrecognized type in params: {:?}",
args[0]
)))
}
}
match &args[1] {
Spanned { item: v, .. } => {
self.value = Some(v.clone());
}
}
}
Ok(())
}
fn filter(&mut self, input: Spanned<Value>) -> Result<Vec<ReturnValue>, ShellError> {
Ok(vec![ReturnSuccess::value(self.add(input)?)])
}
}
fn main() {
serve_plugin(&mut Add::new());
}

89
src/plugins/edit.rs Normal file
View file

@ -0,0 +1,89 @@
use indexmap::IndexMap;
use nu::{
serve_plugin, CallInfo, CommandConfig, Plugin, PositionalType, Primitive, ReturnSuccess,
ReturnValue, ShellError, Spanned, Value,
};
struct Edit {
field: Option<String>,
value: Option<Value>,
}
impl Edit {
fn new() -> Edit {
Edit {
field: None,
value: None,
}
}
fn edit(&self, value: Spanned<Value>) -> Result<Spanned<Value>, ShellError> {
match (value.item, self.value.clone()) {
(obj @ Value::Object(_), Some(v)) => match &self.field {
Some(f) => match obj.replace_data_at_path(value.span, &f, v) {
Some(v) => return Ok(v),
None => {
return Err(ShellError::string(
"edit could not find place to insert field",
))
}
},
None => Err(ShellError::string(
"edit needs a field when adding a value to an object",
)),
},
x => Err(ShellError::string(format!(
"Unrecognized type in stream: {:?}",
x
))),
}
}
}
impl Plugin for Edit {
fn config(&mut self) -> Result<CommandConfig, ShellError> {
Ok(CommandConfig {
name: "edit".to_string(),
positional: vec![
PositionalType::mandatory_any("Field"),
PositionalType::mandatory_any("Value"),
],
is_filter: true,
is_sink: false,
named: IndexMap::new(),
rest_positional: true,
})
}
fn begin_filter(&mut self, call_info: CallInfo) -> Result<(), ShellError> {
if let Some(args) = call_info.args.positional {
match &args[0] {
Spanned {
item: Value::Primitive(Primitive::String(s)),
..
} => {
self.field = Some(s.clone());
}
_ => {
return Err(ShellError::string(format!(
"Unrecognized type in params: {:?}",
args[0]
)))
}
}
match &args[1] {
Spanned { item: v, .. } => {
self.value = Some(v.clone());
}
}
}
Ok(())
}
fn filter(&mut self, input: Spanned<Value>) -> Result<Vec<ReturnValue>, ShellError> {
Ok(vec![ReturnSuccess::value(self.edit(input)?)])
}
}
fn main() {
serve_plugin(&mut Edit::new());
}

View file

@ -4,10 +4,12 @@ use helpers::in_directory as cwd;
#[test]
fn external_num() {
nu!(output,
nu!(
output,
cwd("tests/fixtures/formats"),
"open sgml_description.json | get glossary.GlossDiv.GlossList.GlossEntry.Height | echo $it");
"open sgml_description.json | get glossary.GlossDiv.GlossList.GlossEntry.Height | echo $it"
);
assert_eq!(output, "10");
}
@ -19,3 +21,21 @@ fn inc_plugin() {
assert_eq!(output, "11");
}
#[test]
fn add_plugin() {
nu!(output,
cwd("tests/fixtures/formats"),
"open cargo_sample.toml | add dev-dependencies.newdep \"1\" | get dev-dependencies.newdep | echo $it");
assert_eq!(output, "1");
}
#[test]
fn edit_plugin() {
nu!(output,
cwd("tests/fixtures/formats"),
"open cargo_sample.toml | edit dev-dependencies.pretty_assertions \"7\" | get dev-dependencies.pretty_assertions | echo $it");
assert_eq!(output, "7");
}