mirror of
https://github.com/nushell/nushell
synced 2024-12-26 13:03:07 +00:00
Add and edit functionality
This commit is contained in:
parent
12a785f2a2
commit
ec7d49b0d2
8 changed files with 270 additions and 32 deletions
|
@ -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"
|
||||
|
|
|
@ -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 |
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -36,8 +36,7 @@ pub fn to_toml(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
|||
|
||||
Ok(out
|
||||
.values
|
||||
.map(move |a| {
|
||||
match toml::to_string(&value_to_toml_value(&a)) {
|
||||
.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),
|
||||
|
@ -45,27 +44,9 @@ pub fn to_toml(args: CommandArgs) -> Result<OutputStream, ShellError> {
|
|||
}
|
||||
|
||||
Err(err) => Err(ShellError::type_error(
|
||||
"serializable to toml",
|
||||
"Can not convert to a TOML string",
|
||||
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",
|
||||
}
|
||||
// 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,
|
||||
// )),
|
||||
)),
|
||||
})
|
||||
.to_output_stream())
|
||||
}
|
||||
|
|
|
@ -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
89
src/plugins/add.rs
Normal 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
89
src/plugins/edit.rs
Normal 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());
|
||||
}
|
|
@ -4,9 +4,11 @@ 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");
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue