From 624edce4f75f9ce01587fe29e21d8e823371ec77 Mon Sep 17 00:00:00 2001 From: JT Date: Fri, 29 Oct 2021 19:26:29 +1300 Subject: [PATCH 1/2] Add 'to json' --- crates/nu-command/src/default_context.rs | 2 + crates/nu-command/src/example_test.rs | 3 + crates/nu-command/src/formats/mod.rs | 2 + crates/nu-command/src/formats/to/command.rs | 30 +++ crates/nu-command/src/formats/to/json.rs | 235 ++++++++++++++++++++ crates/nu-command/src/formats/to/mod.rs | 5 + crates/nu-json/src/ser.rs | 27 ++- 7 files changed, 293 insertions(+), 11 deletions(-) create mode 100644 crates/nu-command/src/formats/to/command.rs create mode 100644 crates/nu-command/src/formats/to/json.rs create mode 100644 crates/nu-command/src/formats/to/mod.rs diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 93e9e49f9a..77993b7e55 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -67,6 +67,8 @@ pub fn create_default_context() -> EngineState { SplitRow, Sys, Table, + To, + ToJson, Touch, Use, Where, diff --git a/crates/nu-command/src/example_test.rs b/crates/nu-command/src/example_test.rs index 3685af3996..f5879d5c96 100644 --- a/crates/nu-command/src/example_test.rs +++ b/crates/nu-command/src/example_test.rs @@ -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)); diff --git a/crates/nu-command/src/formats/mod.rs b/crates/nu-command/src/formats/mod.rs index ad1e005635..86f06f85fd 100644 --- a/crates/nu-command/src/formats/mod.rs +++ b/crates/nu-command/src/formats/mod.rs @@ -1,3 +1,5 @@ mod from; +mod to; pub use from::*; +pub use to::*; diff --git a/crates/nu-command/src/formats/to/command.rs b/crates/nu-command/src/formats/to/command.rs new file mode 100644 index 0000000000..ebc5e6b8c6 --- /dev/null +++ b/crates/nu-command/src/formats/to/command.rs @@ -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 { + Ok(PipelineData::new()) + } +} diff --git a/crates/nu-command/src/formats/to/json.rs b/crates/nu-command/src/formats/to/json.rs new file mode 100644 index 0000000000..952c029bdb --- /dev/null +++ b/crates/nu-command/src/formats/to/json.rs @@ -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 { + to_json(engine_state, call, input) + } + + fn examples(&self) -> Vec { + 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 { + 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::, 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, 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 { + let name_span = call.head; + // let pretty: Option = args.get_flag("pretty")?; + + //let input: Vec = 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_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 {}) + } +} diff --git a/crates/nu-command/src/formats/to/mod.rs b/crates/nu-command/src/formats/to/mod.rs new file mode 100644 index 0000000000..13c814dd99 --- /dev/null +++ b/crates/nu-command/src/formats/to/mod.rs @@ -0,0 +1,5 @@ +mod command; +mod json; + +pub use command::To; +pub use json::ToJson; diff --git a/crates/nu-json/src/ser.rs b/crates/nu-json/src/ser.rs index 01ea63e862..172784c3cb 100644 --- a/crates/nu-json/src/ser.rs +++ b/crates/nu-json/src/ser.rs @@ -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(&mut self, writer: &mut W, _: bool) -> Result<()> + fn comma(&mut self, writer: &mut W, first: bool) -> Result<()> where W: io::Write, { - writer.write_all(b"\n")?; + 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) { - escape_bytes(wr, value.as_bytes()).map_err(From::from) - } else { - wr.write_all(value.as_bytes()).map_err(From::from) - } + //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) + // } } #[inline] From cf3f3fde9273858b1e984469f4de2d48b7d27b1f Mon Sep 17 00:00:00 2001 From: JT Date: Sat, 30 Oct 2021 07:15:17 +1300 Subject: [PATCH 2/2] Add some support for --- crates/nu-engine/src/eval.rs | 73 ++++++++++++++++++- crates/nu-parser/src/parser.rs | 10 +++ crates/nu-protocol/src/engine/engine_state.rs | 2 +- src/main.rs | 8 ++ 4 files changed, 88 insertions(+), 5 deletions(-) diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index 2a568b7901..396eab3839 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -1,7 +1,7 @@ use nu_protocol::ast::{Block, Call, Expr, Expression, Operator, Statement}; use nu_protocol::engine::{EngineState, Stack}; use nu_protocol::{ - IntoPipelineData, PipelineData, Range, ShellError, Span, Spanned, Type, Unit, Value, + IntoPipelineData, PipelineData, Range, ShellError, Span, Spanned, Type, Unit, Value, VarId, }; use crate::get_full_help; @@ -210,9 +210,7 @@ pub fn eval_expression( span: expr.span, }) } - Expr::Var(var_id) => stack - .get_var(*var_id) - .map_err(move |_| ShellError::VariableNotFoundAtRuntime(expr.span)), + Expr::Var(var_id) => eval_variable(engine_state, stack, *var_id, expr.span), Expr::VarDecl(_) => Ok(Value::Nothing { span: expr.span }), Expr::CellPath(cell_path) => Ok(Value::CellPath { val: cell_path.clone(), @@ -372,6 +370,73 @@ pub fn eval_block( Ok(input) } +pub fn eval_variable( + _engine_state: &EngineState, + stack: &Stack, + var_id: VarId, + span: Span, +) -> Result { + if var_id == 0 { + // $nu + let mut output_cols = vec![]; + let mut output_vals = vec![]; + + let env_columns: Vec<_> = stack.get_env_vars().keys().map(|x| x.to_string()).collect(); + let env_values: Vec<_> = stack + .get_env_vars() + .values() + .map(|x| Value::String { + val: x.to_string(), + span, + }) + .collect(); + + output_cols.push("env".into()); + output_vals.push(Value::Record { + cols: env_columns, + vals: env_values, + span, + }); + + if let Some(mut config_path) = nu_path::config_dir() { + config_path.push("nushell"); + + let mut history_path = config_path.clone(); + + history_path.push("history.txt"); + + output_cols.push("history-path".into()); + output_vals.push(Value::String { + val: history_path.to_string_lossy().to_string(), + span, + }); + + config_path.push("config.nu"); + + output_cols.push("config-path".into()); + output_vals.push(Value::String { + val: config_path.to_string_lossy().to_string(), + span, + }); + } + + if let Ok(cwd) = std::env::var("PWD") { + output_cols.push("pwd".into()); + output_vals.push(Value::String { val: cwd, span }) + } + + Ok(Value::Record { + cols: output_cols, + vals: output_vals, + span, + }) + } else { + stack + .get_var(var_id) + .map_err(move |_| ShellError::VariableNotFoundAtRuntime(span)) + } +} + pub fn compute(size: i64, unit: Unit, span: Span) -> Value { match unit { Unit::Byte => Value::Filesize { val: size, span }, diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 893c5f492e..14a437d8e0 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -1182,6 +1182,16 @@ pub fn parse_variable_expr( }, None, ); + } else if contents == b"$nu" { + return ( + Expression { + expr: Expr::Var(0), + span, + ty: Type::Unknown, + custom_completion: None, + }, + None, + ); } let (id, err) = parse_variable(working_set, span); diff --git a/crates/nu-protocol/src/engine/engine_state.rs b/crates/nu-protocol/src/engine/engine_state.rs index c9333ec7d7..36d1fe5227 100644 --- a/crates/nu-protocol/src/engine/engine_state.rs +++ b/crates/nu-protocol/src/engine/engine_state.rs @@ -102,7 +102,7 @@ impl EngineState { Self { files: im::vector![], file_contents: im::vector![], - vars: im::vector![], + vars: im::vector![Type::Unknown], decls: im::vector![], blocks: im::vector![], scope: im::vector![ScopeFrame::new()], diff --git a/src/main.rs b/src/main.rs index 5fff81f558..aecb0041aa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -122,6 +122,10 @@ fn main() -> Result<()> { let mut stack = nu_protocol::engine::Stack::new(); + for (k, v) in std::env::vars() { + stack.env_vars.insert(k, v); + } + match eval_block(&engine_state, &mut stack, &block, PipelineData::new()) { Ok(pipeline_data) => { println!("{}", pipeline_data.collect_string()); @@ -146,6 +150,10 @@ fn main() -> Result<()> { let mut nu_prompt = NushellPrompt::new(); let mut stack = nu_protocol::engine::Stack::new(); + for (k, v) in std::env::vars() { + stack.env_vars.insert(k, v); + } + // Load config startup file if let Some(mut config_path) = nu_path::config_dir() { config_path.push("nushell");