Add 'to json'

This commit is contained in:
JT 2021-10-29 19:26:29 +13:00
parent 520d9e1fb6
commit 624edce4f7
7 changed files with 293 additions and 11 deletions

View File

@ -67,6 +67,8 @@ pub fn create_default_context() -> EngineState {
SplitRow,
Sys,
Table,
To,
ToJson,
Touch,
Use,
Where,

View File

@ -5,6 +5,8 @@ use nu_protocol::{
PipelineData,
};
use crate::To;
use super::{From, Into, Math, Split};
pub fn test_examples(cmd: impl Command + 'static) {
@ -16,6 +18,7 @@ pub fn test_examples(cmd: impl Command + 'static) {
// Try to keep this working set small to keep tests running as fast as possible
let mut working_set = StateWorkingSet::new(&*engine_state);
working_set.add_decl(Box::new(From));
working_set.add_decl(Box::new(To));
working_set.add_decl(Box::new(Into));
working_set.add_decl(Box::new(Split));
working_set.add_decl(Box::new(Math));

View File

@ -1,3 +1,5 @@
mod from;
mod to;
pub use from::*;
pub use to::*;

View File

@ -0,0 +1,30 @@
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{PipelineData, ShellError, Signature};
#[derive(Clone)]
pub struct To;
impl Command for To {
fn name(&self) -> &str {
"to"
}
fn usage(&self) -> &str {
"Translate structured data to a format"
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("to")
}
fn run(
&self,
_engine_state: &EngineState,
_stack: &mut Stack,
_call: &Call,
_input: PipelineData,
) -> Result<nu_protocol::PipelineData, ShellError> {
Ok(PipelineData::new())
}
}

View File

@ -0,0 +1,235 @@
use nu_protocol::ast::{Call, PathMember};
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, ShellError, Signature,
Value,
};
#[derive(Clone)]
pub struct ToJson;
impl Command for ToJson {
fn name(&self) -> &str {
"to json"
}
fn signature(&self) -> Signature {
Signature::build("to json")
// .named(
// "pretty",
// SyntaxShape::Int,
// "Formats the JSON text with the provided indentation setting",
// Some('p'),
// )
}
fn usage(&self) -> &str {
"Converts table data into JSON text."
}
fn run(
&self,
engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<nu_protocol::PipelineData, ShellError> {
to_json(engine_state, call, input)
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description:
"Outputs an unformatted JSON string representing the contents of this table",
example: "[1 2 3] | to json",
result: Some(Value::test_string("[\n 1,\n 2,\n 3\n]")),
}]
}
}
pub fn value_to_json_value(v: &Value) -> Result<nu_json::Value, ShellError> {
Ok(match v {
Value::Bool { val, .. } => nu_json::Value::Bool(*val),
Value::Filesize { val, .. } => nu_json::Value::I64(*val),
Value::Duration { val, .. } => nu_json::Value::I64(*val),
Value::Date { val, .. } => nu_json::Value::String(val.to_string()),
Value::Float { val, .. } => nu_json::Value::F64(*val),
Value::Int { val, .. } => nu_json::Value::I64(*val),
Value::Nothing { .. } => nu_json::Value::Null,
Value::String { val, .. } => nu_json::Value::String(val.to_string()),
Value::CellPath { val, .. } => nu_json::Value::Array(
val.members
.iter()
.map(|x| match &x {
PathMember::String { val, .. } => Ok(nu_json::Value::String(val.clone())),
PathMember::Int { val, .. } => Ok(nu_json::Value::U64(*val as u64)),
})
.collect::<Result<Vec<nu_json::Value>, ShellError>>()?,
),
Value::List { vals, .. } => nu_json::Value::Array(json_list(vals)?),
Value::Error { error } => return Err(error.clone()),
Value::Block { .. } | Value::Range { .. } => nu_json::Value::Null,
#[cfg(feature = "dataframe")]
UntaggedValue::DataFrame(_) | UntaggedValue::FrameStruct(_) => serde_json::Value::Null,
Value::Binary { val, .. } => {
nu_json::Value::Array(val.iter().map(|x| nu_json::Value::U64(*x as u64)).collect())
}
Value::Record { cols, vals, .. } => {
let mut m = nu_json::Map::new();
for (k, v) in cols.iter().zip(vals) {
m.insert(k.clone(), value_to_json_value(v)?);
}
nu_json::Value::Object(m)
}
})
}
fn json_list(input: &[Value]) -> Result<Vec<nu_json::Value>, ShellError> {
let mut out = vec![];
for value in input {
out.push(value_to_json_value(value)?);
}
Ok(out)
}
fn to_json(
engine_state: &EngineState,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let name_span = call.head;
// let pretty: Option<Value> = args.get_flag("pretty")?;
//let input: Vec<Value> = input.collect();
// let to_process_input = match input.len() {
// x if x > 1 => {
// let tag = input[0].tag.clone();
// vec![Value:: {
// value: UntaggedValue::Table(input),
// tag,
// }]
// }
// 1 => input,
// _ => vec![],
// };
match input {
PipelineData::Value(value) => {
let json_value = value_to_json_value(&value)?;
match nu_json::to_string(&json_value) {
Ok(serde_json_string) => Ok(Value::String {
val: serde_json_string,
span: name_span,
}
.into_pipeline_data()),
_ => Ok(Value::Error {
error: ShellError::CantConvert("JSON".into(), name_span),
}
.into_pipeline_data()),
}
}
PipelineData::Stream(stream) => Ok(stream
.map(move |value| {
if let Ok(json_value) = value_to_json_value(&value) {
match nu_json::to_string(&json_value) {
Ok(serde_json_string) => Value::String {
val: serde_json_string,
span: name_span,
},
_ => Value::Error {
error: ShellError::CantConvert("JSON".into(), name_span),
},
}
} else {
Value::Error {
error: ShellError::CantConvert("JSON".into(), name_span),
}
}
})
.into_pipeline_data(engine_state.ctrlc.clone())),
}
// input
// // .into_iter()
// .map(
// move |value| {
// let value_span = value.span().expect("non-error");
// match value_to_json_value(&value) {
// Ok(json_value) => {
// match nu_json::to_string(&json_value) {
// Ok(serde_json_string) => {
// // if let Some(pretty_value) = &pretty {
// // let mut pretty_format_failed = true;
// // if let Ok(pretty_u64) = pretty_value.as_u64() {
// // if let Ok(serde_json_value) =
// // serde_json::from_str::<serde_json::Value>(&serde_json_string)
// // {
// // let indentation_string = " ".repeat(pretty_u64 as usize);
// // let serde_formatter =
// // serde_json::ser::PrettyFormatter::with_indent(
// // indentation_string.as_bytes(),
// // );
// // let serde_buffer = Vec::new();
// // let mut serde_serializer =
// // serde_json::Serializer::with_formatter(
// // serde_buffer,
// // serde_formatter,
// // );
// // let serde_json_object = json!(serde_json_value);
// // if let Ok(()) =
// // serde_json_object.serialize(&mut serde_serializer)
// // {
// // if let Ok(ser_json_string) =
// // String::from_utf8(serde_serializer.into_inner())
// // {
// // pretty_format_failed = false;
// // serde_json_string = ser_json_string
// // }
// // }
// // }
// // }
// // if pretty_format_failed {
// // return Value::error(ShellError::labeled_error(
// // "Pretty formatting failed",
// // "failed",
// // pretty_value.tag(),
// // ));
// // }
// // }
// Value::String {
// val: serde_json_string,
// span: value_span,
// }
// }
// _ => Value::Error {
// error: ShellError::CantConvert("JSON".into(), value_span),
// },
// }
// }
// _ => Value::Error {
// error: ShellError::CantConvert("JSON".into(), value_span),
// },
// }
// },
// engine_state.ctrlc.clone(),
// )
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(ToJson {})
}
}

View File

@ -0,0 +1,5 @@
mod command;
mod json;
pub use command::To;
pub use json::ToJson;

View File

@ -9,7 +9,7 @@ use std::num::FpCategory;
use super::error::{Error, ErrorCode, Result};
use serde::ser;
use super::util::ParseNumber;
//use super::util::ParseNumber;
use regex::Regex;
@ -730,11 +730,15 @@ impl<'a> Formatter for HjsonFormatter<'a> {
writer.write_all(&[ch]).map_err(From::from)
}
fn comma<W>(&mut self, writer: &mut W, _: bool) -> Result<()>
fn comma<W>(&mut self, writer: &mut W, first: bool) -> Result<()>
where
W: io::Write,
{
if !first {
writer.write_all(b",\n")?;
} else {
writer.write_all(b"\n")?;
}
indent(writer, self.current_indent, self.indent)
}
@ -848,10 +852,11 @@ where
// Check if we can insert this string without quotes
// see hjson syntax (must not parse as true, false, null or number)
let mut pn = ParseNumber::new(value.bytes());
let is_number = pn.parse(true).is_ok();
//let mut pn = ParseNumber::new(value.bytes());
//let is_number = pn.parse(true).is_ok();
if is_number || NEEDS_QUOTES.is_match(value) || STARTS_WITH_KEYWORD.is_match(value) {
if true {
// is_number || NEEDS_QUOTES.is_match(value) || STARTS_WITH_KEYWORD.is_match(value) {
// First check if the string can be expressed in multiline format or
// we must replace the offending characters with safe escape sequences.
@ -913,11 +918,11 @@ where
}
// Check if we can insert this name without quotes
if NEEDS_ESCAPE_NAME.is_match(value) {
//if NEEDS_ESCAPE_NAME.is_match(value) {
escape_bytes(wr, value.as_bytes()).map_err(From::from)
} else {
wr.write_all(value.as_bytes()).map_err(From::from)
}
// } else {
// wr.write_all(value.as_bytes()).map_err(From::from)
// }
}
#[inline]