diff --git a/TODO.md b/TODO.md index d3dd766e42..22a708b41b 100644 --- a/TODO.md +++ b/TODO.md @@ -35,7 +35,7 @@ - [x] ctrl-c support - [x] operator overflow - [x] Support for `$in` -- [ ] config system +- [x] config system - [ ] shells - [ ] plugins - [ ] dataframes diff --git a/crates/nu-command/src/conversions/into/string.rs b/crates/nu-command/src/conversions/into/string.rs index 533f319d25..318d335346 100644 --- a/crates/nu-command/src/conversions/into/string.rs +++ b/crates/nu-command/src/conversions/into/string.rs @@ -2,7 +2,7 @@ use nu_engine::CallExt; use nu_protocol::{ ast::{Call, CellPath}, engine::{Command, EngineState, Stack}, - Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value, + Config, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value, }; // TODO num_format::SystemLocale once platform-specific dependencies are stable (see Cargo.toml) @@ -136,6 +136,7 @@ fn string_helper( let head = call.head; let decimals_value: Option = call.get_flag(engine_state, stack, "decimals")?; let column_paths: Vec = call.rest(engine_state, stack, 0)?; + let config = stack.get_config()?; if decimals && decimals_value.is_some() && decimals_value.unwrap().is_negative() { return Err(ShellError::UnsupportedInput( @@ -147,13 +148,16 @@ fn string_helper( input.map( move |v| { if column_paths.is_empty() { - action(&v, head, decimals, decimals_value, false) + action(&v, head, decimals, decimals_value, false, &config) } else { let mut ret = v; for path in &column_paths { + let config = config.clone(); let r = ret.update_cell_path( &path.members, - Box::new(move |old| action(old, head, decimals, decimals_value, false)), + Box::new(move |old| { + action(old, head, decimals, decimals_value, false, &config) + }), ); if let Err(error) = r { return Value::Error { error }; @@ -173,6 +177,7 @@ pub fn action( decimals: bool, digits: Option, group_digits: bool, + config: &Config, ) -> Value { match input { Value::Int { val, .. } => { @@ -212,7 +217,7 @@ pub fn action( }, Value::Filesize { val: _, .. } => Value::String { - val: input.clone().into_string(", "), + val: input.clone().into_string(", ", config), span, }, Value::Nothing { .. } => Value::String { diff --git a/crates/nu-command/src/example_test.rs b/crates/nu-command/src/example_test.rs index 7e1f39069f..b9e6f6d535 100644 --- a/crates/nu-command/src/example_test.rs +++ b/crates/nu-command/src/example_test.rs @@ -2,7 +2,7 @@ use nu_engine::eval_block; use nu_parser::parse; use nu_protocol::{ engine::{Command, EngineState, Stack, StateWorkingSet}, - PipelineData, Span, + PipelineData, Span, Value, CONFIG_VARIABLE_ID, }; use crate::To; @@ -57,6 +57,16 @@ pub fn test_examples(cmd: impl Command + 'static) { let mut stack = Stack::new(); + // Set up our initial config to start from + stack.vars.insert( + CONFIG_VARIABLE_ID, + Value::Record { + cols: vec![], + vals: vec![], + span: Span::unknown(), + }, + ); + match eval_block( &engine_state, &mut stack, diff --git a/crates/nu-command/src/formats/from/csv.rs b/crates/nu-command/src/formats/from/csv.rs index 25f8de5369..02b350091c 100644 --- a/crates/nu-command/src/formats/from/csv.rs +++ b/crates/nu-command/src/formats/from/csv.rs @@ -78,6 +78,7 @@ fn from_csv( let noheaders = call.has_flag("noheaders"); let separator: Option = call.get_flag(engine_state, stack, "separator")?; + let config = stack.get_config()?; let sep = match separator { Some(Value::String { val: s, span }) => { @@ -97,7 +98,7 @@ fn from_csv( _ => ',', }; - from_delimited_data(noheaders, sep, input, name) + from_delimited_data(noheaders, sep, input, name, &config) } #[cfg(test)] diff --git a/crates/nu-command/src/formats/from/delimited.rs b/crates/nu-command/src/formats/from/delimited.rs index 4e17206589..eafdd4e53d 100644 --- a/crates/nu-command/src/formats/from/delimited.rs +++ b/crates/nu-command/src/formats/from/delimited.rs @@ -1,5 +1,5 @@ use csv::ReaderBuilder; -use nu_protocol::{IntoPipelineData, PipelineData, ShellError, Span, Value}; +use nu_protocol::{Config, IntoPipelineData, PipelineData, ShellError, Span, Value}; fn from_delimited_string_to_value( s: String, @@ -50,8 +50,9 @@ pub fn from_delimited_data( sep: char, input: PipelineData, name: Span, + config: &Config, ) -> Result { - let concat_string = input.collect_string(""); + let concat_string = input.collect_string("", config); Ok( from_delimited_string_to_value(concat_string, noheaders, sep, name) diff --git a/crates/nu-command/src/formats/from/eml.rs b/crates/nu-command/src/formats/from/eml.rs index 3c8b684b93..9ded63ea67 100644 --- a/crates/nu-command/src/formats/from/eml.rs +++ b/crates/nu-command/src/formats/from/eml.rs @@ -4,6 +4,7 @@ use indexmap::map::IndexMap; use nu_engine::CallExt; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::Config; use nu_protocol::{ Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value, }; @@ -41,7 +42,8 @@ impl Command for FromEml { let head = call.head; let preview_body: Option> = call.get_flag(engine_state, stack, "preview-body")?; - from_eml(input, preview_body, head) + let config = stack.get_config()?; + from_eml(input, preview_body, head, &config) } fn examples(&self) -> Vec { @@ -176,8 +178,9 @@ fn from_eml( input: PipelineData, preview_body: Option>, head: Span, + config: &Config, ) -> Result { - let value = input.collect_string(""); + let value = input.collect_string("", config); let body_preview = preview_body .map(|b| b.item as usize) diff --git a/crates/nu-command/src/formats/from/json.rs b/crates/nu-command/src/formats/from/json.rs index 1cdbda80f3..75c6bcfd3d 100644 --- a/crates/nu-command/src/formats/from/json.rs +++ b/crates/nu-command/src/formats/from/json.rs @@ -72,12 +72,13 @@ impl Command for FromJson { fn run( &self, engine_state: &EngineState, - _stack: &mut Stack, + stack: &mut Stack, call: &Call, input: PipelineData, ) -> Result { let span = call.head; - let mut string_input = input.collect_string(""); + let config = stack.get_config()?; + let mut string_input = input.collect_string("", &config); string_input.push('\n'); // TODO: turn this into a structured underline of the nu_json error diff --git a/crates/nu-command/src/formats/from/tsv.rs b/crates/nu-command/src/formats/from/tsv.rs index 2c07a29b76..4f9a2e7085 100644 --- a/crates/nu-command/src/formats/from/tsv.rs +++ b/crates/nu-command/src/formats/from/tsv.rs @@ -2,7 +2,7 @@ use super::delimited::from_delimited_data; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; -use nu_protocol::{PipelineData, ShellError, Signature}; +use nu_protocol::{Config, PipelineData, ShellError, Signature}; #[derive(Clone)] pub struct FromTsv; @@ -27,20 +27,21 @@ impl Command for FromTsv { fn run( &self, _engine_state: &EngineState, - _stack: &mut Stack, + stack: &mut Stack, call: &Call, input: PipelineData, ) -> Result { - from_tsv(call, input) + let config = stack.get_config()?; + from_tsv(call, input, &config) } } -fn from_tsv(call: &Call, input: PipelineData) -> Result { +fn from_tsv(call: &Call, input: PipelineData, config: &Config) -> Result { let name = call.head; let noheaders = call.has_flag("noheaders"); - from_delimited_data(noheaders, '\t', input, name) + from_delimited_data(noheaders, '\t', input, name, config) } #[cfg(test)] diff --git a/crates/nu-command/src/formats/from/url.rs b/crates/nu-command/src/formats/from/url.rs index 4df1fd9abe..bc113be7bb 100644 --- a/crates/nu-command/src/formats/from/url.rs +++ b/crates/nu-command/src/formats/from/url.rs @@ -1,6 +1,6 @@ use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; -use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, Value}; +use nu_protocol::{Config, Example, PipelineData, ShellError, Signature, Span, Value}; #[derive(Clone)] pub struct FromUrl; @@ -21,12 +21,13 @@ impl Command for FromUrl { fn run( &self, _engine_state: &EngineState, - _stack: &mut Stack, + stack: &mut Stack, call: &Call, input: PipelineData, ) -> Result { let head = call.head; - from_url(input, head) + let config = stack.get_config()?; + from_url(input, head, &config) } fn examples(&self) -> Vec { @@ -52,8 +53,8 @@ impl Command for FromUrl { } } -fn from_url(input: PipelineData, head: Span) -> Result { - let concat_string = input.collect_string(""); +fn from_url(input: PipelineData, head: Span, config: &Config) -> Result { + let concat_string = input.collect_string("", config); let result = serde_urlencoded::from_str::>(&concat_string); diff --git a/crates/nu-command/src/formats/from/yaml.rs b/crates/nu-command/src/formats/from/yaml.rs index 44ddca0096..2114a84bdf 100644 --- a/crates/nu-command/src/formats/from/yaml.rs +++ b/crates/nu-command/src/formats/from/yaml.rs @@ -2,7 +2,7 @@ use itertools::Itertools; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::{ - Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Spanned, Value, + Config, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Spanned, Value, }; use serde::de::Deserialize; use std::collections::HashMap; @@ -65,12 +65,13 @@ impl Command for FromYaml { fn run( &self, _engine_state: &EngineState, - _stack: &mut Stack, + stack: &mut Stack, call: &Call, input: PipelineData, ) -> Result { let head = call.head; - from_yaml(input, head) + let config = stack.get_config()?; + from_yaml(input, head, &config) } } @@ -93,12 +94,13 @@ impl Command for FromYml { fn run( &self, _engine_state: &EngineState, - _stack: &mut Stack, + stack: &mut Stack, call: &Call, input: PipelineData, ) -> Result { let head = call.head; - from_yaml(input, head) + let config = stack.get_config()?; + from_yaml(input, head, &config) } } @@ -202,8 +204,8 @@ pub fn from_yaml_string_to_value(s: String, span: Span) -> Result Result { - let concat_string = input.collect_string(""); +fn from_yaml(input: PipelineData, head: Span, config: &Config) -> Result { + let concat_string = input.collect_string("", config); match from_yaml_string_to_value(concat_string, head) { Ok(x) => Ok(x.into_pipeline_data()), @@ -248,6 +250,7 @@ mod test { }), }, ]; + let config = Config::default(); for tc in tt { let actual = from_yaml_string_to_value(tc.input.to_owned(), Span::unknown()); if actual.is_err() { @@ -259,8 +262,8 @@ mod test { ); } else { assert_eq!( - actual.unwrap().into_string(""), - tc.expected.unwrap().into_string("") + actual.unwrap().into_string("", &config), + tc.expected.unwrap().into_string("", &config) ); } } diff --git a/crates/nu-command/src/strings/build_string.rs b/crates/nu-command/src/strings/build_string.rs index 27aa2e9d32..5c6692a3c6 100644 --- a/crates/nu-command/src/strings/build_string.rs +++ b/crates/nu-command/src/strings/build_string.rs @@ -49,10 +49,13 @@ impl Command for BuildString { call: &Call, _input: PipelineData, ) -> Result { + let config = stack.get_config()?; let output = call .positional .iter() - .map(|expr| eval_expression(engine_state, stack, expr).map(|val| val.into_string(", "))) + .map(|expr| { + eval_expression(engine_state, stack, expr).map(|val| val.into_string(", ", &config)) + }) .collect::, ShellError>>()?; Ok(Value::String { diff --git a/crates/nu-command/src/strings/str_/collect.rs b/crates/nu-command/src/strings/str_/collect.rs index 4394dbe7e8..e4b2fead5b 100644 --- a/crates/nu-command/src/strings/str_/collect.rs +++ b/crates/nu-command/src/strings/str_/collect.rs @@ -34,12 +34,14 @@ impl Command for StrCollect { ) -> Result { let separator: Option = call.opt(engine_state, stack, 0)?; + let config = stack.get_config()?; + // Hmm, not sure what we actually want. If you don't use debug_string, Date comes out as human readable // which feels funny #[allow(clippy::needless_collect)] let strings: Vec = input .into_iter() - .map(|value| value.debug_string("\n")) + .map(|value| value.debug_string("\n", &config)) .collect(); let output = if let Some(separator) = separator { diff --git a/crates/nu-command/src/system/run_external.rs b/crates/nu-command/src/system/run_external.rs index 183f253a54..d0dedaee68 100644 --- a/crates/nu-command/src/system/run_external.rs +++ b/crates/nu-command/src/system/run_external.rs @@ -8,7 +8,7 @@ use std::sync::mpsc; use nu_protocol::engine::{EngineState, Stack}; use nu_protocol::{ast::Call, engine::Command, ShellError, Signature, SyntaxShape, Value}; -use nu_protocol::{IntoInterruptiblePipelineData, PipelineData, Span, Spanned}; +use nu_protocol::{Config, IntoInterruptiblePipelineData, PipelineData, Span, Spanned}; use nu_engine::CallExt; @@ -44,13 +44,15 @@ impl Command for External { let last_expression = call.has_flag("last_expression"); let env_vars = stack.get_env_vars(); + let config = stack.get_config()?; + let command = ExternalCommand { name, args, last_expression, env_vars, }; - command.run_with_input(engine_state, input) + command.run_with_input(engine_state, input, config) } } @@ -66,6 +68,7 @@ impl ExternalCommand { &self, engine_state: &EngineState, input: PipelineData, + config: Config, ) -> Result { let mut process = self.create_command(); @@ -112,7 +115,10 @@ impl ExternalCommand { } } x => { - if stdin_write.write(x.into_string(", ").as_bytes()).is_err() { + if stdin_write + .write(x.into_string(", ", &config).as_bytes()) + .is_err() + { return Err(()); } } diff --git a/crates/nu-command/src/viewers/griddle.rs b/crates/nu-command/src/viewers/griddle.rs index 9d9e3c9eaa..e62315696d 100644 --- a/crates/nu-command/src/viewers/griddle.rs +++ b/crates/nu-command/src/viewers/griddle.rs @@ -3,7 +3,7 @@ use nu_engine::CallExt; use nu_protocol::{ ast::{Call, PathMember}, engine::{Command, EngineState, Stack}, - IntoPipelineData, PipelineData, Signature, Span, SyntaxShape, Value, + Config, IntoPipelineData, PipelineData, Signature, Span, SyntaxShape, Value, }; use nu_term_grid::grid::{Alignment, Cell, Direction, Filling, Grid, GridOptions}; use terminal_size::{Height, Width}; @@ -57,10 +57,12 @@ prints out the list properly."# let color_param: bool = call.has_flag("color"); let separator_param: Option = call.get_flag(engine_state, stack, "separator")?; + let config = stack.get_config()?; + match input { PipelineData::Value(Value::List { vals, .. }) => { // dbg!("value::list"); - let data = convert_to_list2(vals); + let data = convert_to_list2(vals, &config); if let Some(items) = data { Ok(create_grid_output2( items, @@ -75,7 +77,7 @@ prints out the list properly."# } PipelineData::Stream(stream) => { // dbg!("value::stream"); - let data = convert_to_list2(stream); + let data = convert_to_list2(stream, &config); if let Some(items) = data { Ok(create_grid_output2( items, @@ -94,7 +96,7 @@ prints out the list properly."# let mut items = vec![]; for (i, (c, v)) in cols.into_iter().zip(vals.into_iter()).enumerate() { - items.push((i, c, v.into_string(", "))) + items.push((i, c, v.into_string(", ", &config))) } Ok(create_grid_output2( @@ -171,7 +173,10 @@ fn create_grid_output2( .into_pipeline_data() } -fn convert_to_list2(iter: impl IntoIterator) -> Option> { +fn convert_to_list2( + iter: impl IntoIterator, + config: &Config, +) -> Option> { let mut iter = iter.into_iter().peekable(); if let Some(first) = iter.peek() { @@ -187,7 +192,7 @@ fn convert_to_list2(iter: impl IntoIterator) -> Option) -> Option row.push(value.into_string(", ")), + Ok(value) => row.push(value.into_string(", ", config)), Err(_) => row.push(String::new()), } } diff --git a/crates/nu-command/src/viewers/table.rs b/crates/nu-command/src/viewers/table.rs index 985a8bc69e..9a4a7c5938 100644 --- a/crates/nu-command/src/viewers/table.rs +++ b/crates/nu-command/src/viewers/table.rs @@ -1,7 +1,7 @@ use nu_protocol::ast::{Call, PathMember}; use nu_protocol::engine::{Command, EngineState, Stack}; -use nu_protocol::{IntoPipelineData, PipelineData, ShellError, Signature, Span, Value}; -use nu_table::StyledString; +use nu_protocol::{Config, IntoPipelineData, PipelineData, ShellError, Signature, Span, Value}; +use nu_table::{StyledString, Theme}; use std::collections::HashMap; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; @@ -27,11 +27,12 @@ impl Command for Table { fn run( &self, engine_state: &EngineState, - _stack: &mut Stack, + stack: &mut Stack, call: &Call, input: PipelineData, ) -> Result { let ctrlc = engine_state.ctrlc.clone(); + let config = stack.get_config()?; let term_width = if let Some((Width(w), Height(_h))) = terminal_size::terminal_size() { w as usize @@ -41,7 +42,7 @@ impl Command for Table { match input { PipelineData::Value(Value::List { vals, .. }) => { - let table = convert_to_table(vals, ctrlc)?; + let table = convert_to_table(vals, ctrlc, &config)?; if let Some(table) = table { let result = nu_table::draw_table(&table, term_width, &HashMap::new()); @@ -56,7 +57,7 @@ impl Command for Table { } } PipelineData::Stream(stream) => { - let table = convert_to_table(stream, ctrlc)?; + let table = convert_to_table(stream, ctrlc, &config)?; if let Some(table) = table { let result = nu_table::draw_table(&table, term_width, &HashMap::new()); @@ -80,7 +81,7 @@ impl Command for Table { style: nu_table::TextStyle::default_field(), }, StyledString { - contents: v.into_string(", "), + contents: v.into_string(", ", &config), style: nu_table::TextStyle::default(), }, ]) @@ -89,7 +90,7 @@ impl Command for Table { let table = nu_table::Table { headers: vec![], data: output, - theme: nu_table::Theme::rounded(), + theme: load_theme_from_config(&config), }; let result = nu_table::draw_table(&table, term_width, &HashMap::new()); @@ -109,6 +110,7 @@ impl Command for Table { fn convert_to_table( iter: impl IntoIterator, ctrlc: Option>, + config: &Config, ) -> Result, ShellError> { let mut iter = iter.into_iter().peekable(); @@ -133,7 +135,7 @@ fn convert_to_table( let mut row = vec![row_num.to_string()]; if headers.is_empty() { - row.push(item.into_string(", ")) + row.push(item.into_string(", ", config)) } else { for header in headers.iter().skip(1) { let result = match item { @@ -147,7 +149,7 @@ fn convert_to_table( }; match result { - Ok(value) => row.push(value.into_string(", ")), + Ok(value) => row.push(value.into_string(", ", config)), Err(_) => row.push(String::new()), } } @@ -185,9 +187,24 @@ fn convert_to_table( .collect::>() }) .collect(), - theme: nu_table::Theme::rounded(), + theme: load_theme_from_config(config), })) } else { Ok(None) } } + +fn load_theme_from_config(config: &Config) -> Theme { + match config.table_mode.as_str() { + "basic" => nu_table::Theme::basic(), + "compact" => nu_table::Theme::compact(), + "compact_double" => nu_table::Theme::compact_double(), + "light" => nu_table::Theme::light(), + "with_love" => nu_table::Theme::with_love(), + "rounded" => nu_table::Theme::rounded(), + "reinforced" => nu_table::Theme::reinforced(), + "heavy" => nu_table::Theme::heavy(), + "none" => nu_table::Theme::none(), + _ => nu_table::Theme::rounded(), + } +} diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index c411a2d4fe..1dd4ad8781 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -429,7 +429,9 @@ pub fn eval_subexpression( // to be used later // FIXME: the trimming of the end probably needs to live in a better place - let mut s = input.collect_string(""); + let config = stack.get_config()?; + + let mut s = input.collect_string("", &config); if s.ends_with('\n') { s.pop(); } diff --git a/crates/nu-engine/src/from_value.rs b/crates/nu-engine/src/from_value.rs index 66c931774b..281796a7ce 100644 --- a/crates/nu-engine/src/from_value.rs +++ b/crates/nu-engine/src/from_value.rs @@ -95,14 +95,32 @@ impl FromValue for f64 { impl FromValue for String { fn from_value(v: &Value) -> Result { // FIXME: we may want to fail a little nicer here - Ok(v.clone().into_string(", ")) + match v { + Value::CellPath { val, .. } => Ok(val.into_string()), + Value::String { val, .. } => Ok(val.clone()), + v => Err(ShellError::CantConvert( + "string".into(), + v.get_type().to_string(), + v.span()?, + )), + } } } impl FromValue for Spanned { fn from_value(v: &Value) -> Result { Ok(Spanned { - item: v.clone().into_string(", "), + item: match v { + Value::CellPath { val, .. } => val.into_string(), + Value::String { val, .. } => val.clone(), + v => { + return Err(ShellError::CantConvert( + "string".into(), + v.get_type().to_string(), + v.span()?, + )) + } + }, span: v.span()?, }) } diff --git a/crates/nu-parser/src/parse_keywords.rs b/crates/nu-parser/src/parse_keywords.rs index 2a138bbc26..030ef73882 100644 --- a/crates/nu-parser/src/parse_keywords.rs +++ b/crates/nu-parser/src/parse_keywords.rs @@ -1,7 +1,7 @@ use nu_protocol::{ ast::{Block, Call, Expr, Expression, ImportPattern, ImportPatternMember, Pipeline, Statement}, engine::StateWorkingSet, - span, DeclId, Span, SyntaxShape, Type, + span, DeclId, Span, SyntaxShape, Type, CONFIG_VARIABLE_ID, }; use std::path::Path; @@ -800,7 +800,9 @@ pub fn parse_let( .expect("internal error: expected variable"); let rhs_type = call.positional[1].ty.clone(); - working_set.set_variable_type(var_id, rhs_type); + if var_id != CONFIG_VARIABLE_ID { + working_set.set_variable_type(var_id, rhs_type); + } } return ( diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index e413e28b19..3f958b3bb3 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -12,6 +12,7 @@ use nu_protocol::{ }, engine::StateWorkingSet, span, Flag, PositionalArg, Signature, Span, Spanned, SyntaxShape, Type, Unit, VarId, + CONFIG_VARIABLE_ID, }; use crate::parse_keywords::{ @@ -1201,6 +1202,16 @@ pub fn parse_variable_expr( }, None, ); + } else if contents == b"$config" { + return ( + Expression { + expr: Expr::Var(nu_protocol::CONFIG_VARIABLE_ID), + span, + ty: Type::Unknown, + custom_completion: None, + }, + None, + ); } let (id, err) = parse_variable(working_set, span); @@ -1909,6 +1920,16 @@ pub fn parse_var_with_opt_type( Some(ParseError::MissingType(spans[*spans_idx])), ) } + } else if bytes == b"$config" || bytes == b"config" { + ( + Expression { + expr: Expr::Var(CONFIG_VARIABLE_ID), + span: spans[*spans_idx], + ty: Type::Unknown, + custom_completion: None, + }, + None, + ) } else { let id = working_set.add_variable(bytes, Type::Unknown); diff --git a/crates/nu-protocol/src/config.rs b/crates/nu-protocol/src/config.rs new file mode 100644 index 0000000000..ff9cb506cf --- /dev/null +++ b/crates/nu-protocol/src/config.rs @@ -0,0 +1,40 @@ +use serde::{Deserialize, Serialize}; + +use crate::{ShellError, Value}; + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct Config { + pub filesize_metric: bool, + pub table_mode: String, +} + +impl Default for Config { + fn default() -> Config { + Config { + filesize_metric: false, + table_mode: "rounded".into(), + } + } +} + +impl Value { + pub fn into_config(self) -> Result { + let v = self.as_record()?; + + let mut config = Config::default(); + + for (key, value) in v.0.iter().zip(v.1) { + match key.as_str() { + "filesize_metric" => { + config.filesize_metric = value.as_bool()?; + } + "table_mode" => { + config.table_mode = value.as_string()?; + } + _ => {} + } + } + + Ok(config) + } +} diff --git a/crates/nu-protocol/src/engine/engine_state.rs b/crates/nu-protocol/src/engine/engine_state.rs index cbe3548448..3929357992 100644 --- a/crates/nu-protocol/src/engine/engine_state.rs +++ b/crates/nu-protocol/src/engine/engine_state.rs @@ -136,13 +136,14 @@ pub struct EngineState { pub const NU_VARIABLE_ID: usize = 0; pub const SCOPE_VARIABLE_ID: usize = 1; pub const IN_VARIABLE_ID: usize = 2; +pub const CONFIG_VARIABLE_ID: usize = 3; impl EngineState { pub fn new() -> Self { Self { files: im::vector![], file_contents: im::vector![], - vars: im::vector![Type::Unknown, Type::Unknown, Type::Unknown], + vars: im::vector![Type::Unknown, Type::Unknown, Type::Unknown, Type::Unknown], decls: im::vector![], blocks: im::vector![], scope: im::vector![ScopeFrame::new()], diff --git a/crates/nu-protocol/src/engine/stack.rs b/crates/nu-protocol/src/engine/stack.rs index f2fc9fc0ac..cc3badbf8f 100644 --- a/crates/nu-protocol/src/engine/stack.rs +++ b/crates/nu-protocol/src/engine/stack.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use crate::{ShellError, Value, VarId}; +use crate::{Config, ShellError, Value, VarId, CONFIG_VARIABLE_ID}; /// A runtime value stack used during evaluation /// @@ -42,6 +42,7 @@ impl Stack { if let Some(v) = self.vars.get(&var_id) { return Ok(v.clone()); } + Err(ShellError::InternalError("variable not found".into())) } @@ -67,6 +68,11 @@ impl Stack { // FIXME: this is probably slow output.env_vars = self.env_vars.clone(); + let config = self + .get_var(CONFIG_VARIABLE_ID) + .expect("internal error: config is missing"); + output.vars.insert(CONFIG_VARIABLE_ID, config); + output } @@ -81,6 +87,18 @@ impl Stack { None } + pub fn get_config(&self) -> Result { + let config = self.get_var(CONFIG_VARIABLE_ID); + + match config { + Ok(config) => config.into_config(), + Err(e) => { + println!("Can't find {} in {:?}", CONFIG_VARIABLE_ID, self); + Err(e) + } + } + } + pub fn print_stack(&self) { println!("vars:"); for (var, val) in &self.vars { diff --git a/crates/nu-protocol/src/lib.rs b/crates/nu-protocol/src/lib.rs index bb1e775ecc..89f6896dd9 100644 --- a/crates/nu-protocol/src/lib.rs +++ b/crates/nu-protocol/src/lib.rs @@ -1,4 +1,5 @@ pub mod ast; +mod config; pub mod engine; mod example; mod id; @@ -11,7 +12,8 @@ mod ty; mod value; pub use value::Value; -pub use engine::{IN_VARIABLE_ID, NU_VARIABLE_ID, SCOPE_VARIABLE_ID}; +pub use config::*; +pub use engine::{CONFIG_VARIABLE_ID, IN_VARIABLE_ID, NU_VARIABLE_ID, SCOPE_VARIABLE_ID}; pub use example::*; pub use id::*; pub use pipeline_data::*; diff --git a/crates/nu-protocol/src/pipeline_data.rs b/crates/nu-protocol/src/pipeline_data.rs index ba7a6978eb..67f63cad3c 100644 --- a/crates/nu-protocol/src/pipeline_data.rs +++ b/crates/nu-protocol/src/pipeline_data.rs @@ -1,6 +1,6 @@ use std::sync::{atomic::AtomicBool, Arc}; -use crate::{ast::PathMember, ShellError, Span, Value, ValueStream}; +use crate::{ast::PathMember, Config, ShellError, Span, Value, ValueStream}; /// The foundational abstraction for input and output to commands /// @@ -51,10 +51,10 @@ impl PipelineData { } } - pub fn collect_string(self, separator: &str) -> String { + pub fn collect_string(self, separator: &str, config: &Config) -> String { match self { - PipelineData::Value(v) => v.into_string(separator), - PipelineData::Stream(s) => s.into_string(separator), + PipelineData::Value(v) => v.into_string(separator, config), + PipelineData::Stream(s) => s.into_string(separator, config), } } diff --git a/crates/nu-protocol/src/shell_error.rs b/crates/nu-protocol/src/shell_error.rs index fdbca5e547..54637cbfb3 100644 --- a/crates/nu-protocol/src/shell_error.rs +++ b/crates/nu-protocol/src/shell_error.rs @@ -80,7 +80,7 @@ pub enum ShellError { #[diagnostic(code(nu::shell::internal_error), url(docsrs))] InternalError(String), - #[error("Variable not found")] + #[error("Variable not found!!!")] #[diagnostic(code(nu::shell::variable_not_found), url(docsrs))] VariableNotFoundAtRuntime(#[label = "variable not found"] Span), diff --git a/crates/nu-protocol/src/value/mod.rs b/crates/nu-protocol/src/value/mod.rs index 7724076686..463777d48c 100644 --- a/crates/nu-protocol/src/value/mod.rs +++ b/crates/nu-protocol/src/value/mod.rs @@ -15,7 +15,7 @@ use std::collections::HashMap; use std::{cmp::Ordering, fmt::Debug}; use crate::ast::{CellPath, PathMember}; -use crate::{did_you_mean, span, BlockId, Span, Spanned, Type}; +use crate::{did_you_mean, span, BlockId, Config, Span, Spanned, Type}; use crate::ShellError; @@ -106,6 +106,28 @@ impl Value { } } + pub fn as_record(&self) -> Result<(&[String], &[Value]), ShellError> { + match self { + Value::Record { cols, vals, .. } => Ok((cols, vals)), + x => Err(ShellError::CantConvert( + "record".into(), + x.get_type().to_string(), + self.span()?, + )), + } + } + + pub fn as_bool(&self) -> Result { + match self { + Value::Bool { val, .. } => Ok(*val), + x => Err(ShellError::CantConvert( + "boolean".into(), + x.get_type().to_string(), + self.span()?, + )), + } + } + /// Get the span for the current value pub fn span(&self) -> Result { match self { @@ -174,26 +196,26 @@ impl Value { } /// Convert Value into string. Note that Streams will be consumed. - pub fn into_string(self, separator: &str) -> String { + pub fn into_string(self, separator: &str, config: &Config) -> String { match self { Value::Bool { val, .. } => val.to_string(), Value::Int { val, .. } => val.to_string(), Value::Float { val, .. } => val.to_string(), - Value::Filesize { val, .. } => format_filesize(val), + Value::Filesize { val, .. } => format_filesize(val, config), Value::Duration { val, .. } => format_duration(val), Value::Date { val, .. } => HumanTime::from(val).to_string(), Value::Range { val, .. } => { format!( "{}..{}", - val.from.into_string(", "), - val.to.into_string(", ") + val.from.into_string(", ", config), + val.to.into_string(", ", config) ) } Value::String { val, .. } => val, Value::List { vals: val, .. } => format!( "[{}]", val.into_iter() - .map(|x| x.into_string(", ")) + .map(|x| x.into_string(", ", config)) .collect::>() .join(separator) ), @@ -201,7 +223,7 @@ impl Value { "{{{}}}", cols.iter() .zip(vals.iter()) - .map(|(x, y)| format!("{}: {}", x, y.clone().into_string(", "))) + .map(|(x, y)| format!("{}: {}", x, y.clone().into_string(", ", config))) .collect::>() .join(separator) ), @@ -214,26 +236,26 @@ impl Value { } /// Convert Value into string. Note that Streams will be consumed. - pub fn debug_string(self, separator: &str) -> String { + pub fn debug_string(self, separator: &str, config: &Config) -> String { match self { Value::Bool { val, .. } => val.to_string(), Value::Int { val, .. } => val.to_string(), Value::Float { val, .. } => val.to_string(), - Value::Filesize { val, .. } => format_filesize(val), + Value::Filesize { val, .. } => format_filesize(val, config), Value::Duration { val, .. } => format_duration(val), Value::Date { val, .. } => format!("{:?}", val), Value::Range { val, .. } => { format!( "{}..{}", - val.from.into_string(", "), - val.to.into_string(", ") + val.from.into_string(", ", config), + val.to.into_string(", ", config) ) } Value::String { val, .. } => val, Value::List { vals: val, .. } => format!( "[{}]", val.into_iter() - .map(|x| x.into_string(", ")) + .map(|x| x.into_string(", ", config)) .collect::>() .join(separator) ), @@ -241,7 +263,7 @@ impl Value { "{{{}}}", cols.iter() .zip(vals.iter()) - .map(|(x, y)| format!("{}: {}", x, y.clone().into_string(", "))) + .map(|(x, y)| format!("{}: {}", x, y.clone().into_string(", ", config))) .collect::>() .join(separator) ), @@ -1171,14 +1193,14 @@ pub fn format_duration(duration: i64) -> String { ) } -fn format_filesize(num_bytes: i64) -> String { +fn format_filesize(num_bytes: i64, config: &Config) -> String { let byte = byte_unit::Byte::from_bytes(num_bytes as u128); if byte.get_bytes() == 0u128 { return "—".to_string(); } - let byte = byte.get_appropriate_unit(false); + let byte = byte.get_appropriate_unit(config.filesize_metric); match byte.get_unit() { byte_unit::ByteUnit::B => format!("{} B ", byte.get_value()), diff --git a/crates/nu-protocol/src/value/row.rs b/crates/nu-protocol/src/value/row.rs index 462739d94d..231f8d8669 100644 --- a/crates/nu-protocol/src/value/row.rs +++ b/crates/nu-protocol/src/value/row.rs @@ -6,7 +6,7 @@ use crate::*; pub struct RowStream(Rc>>>); impl RowStream { - pub fn into_string(self, headers: Vec) -> String { + pub fn into_string(self, headers: Vec, config: &Config) -> String { format!( "[{}]\n[{}]", headers @@ -16,7 +16,7 @@ impl RowStream { .join(", "), self.map(|x: Vec| { x.into_iter() - .map(|x| x.into_string(", ")) + .map(|x| x.into_string(", ", config)) .collect::>() .join(", ") }) diff --git a/crates/nu-protocol/src/value/stream.rs b/crates/nu-protocol/src/value/stream.rs index f49965a44d..728bd0f567 100644 --- a/crates/nu-protocol/src/value/stream.rs +++ b/crates/nu-protocol/src/value/stream.rs @@ -19,8 +19,8 @@ pub struct ValueStream { } impl ValueStream { - pub fn into_string(self, separator: &str) -> String { - self.map(|x: Value| x.into_string(", ")) + pub fn into_string(self, separator: &str, config: &Config) -> String { + self.map(|x: Value| x.into_string(", ", config)) .collect::>() .join(separator) } diff --git a/src/main.rs b/src/main.rs index 10cf327483..c54c9ccf36 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,7 +19,7 @@ use nu_parser::parse; use nu_protocol::{ ast::Call, engine::{EngineState, Stack, StateWorkingSet}, - IntoPipelineData, PipelineData, ShellError, Span, Value, + IntoPipelineData, PipelineData, ShellError, Span, Value, CONFIG_VARIABLE_ID, }; use reedline::{Completer, CompletionActionHandler, DefaultPrompt, LineBuffer, Prompt}; @@ -81,7 +81,7 @@ impl CompletionActionHandler for FuzzyCompletion { } fn main() -> Result<()> { - miette::set_panic_hook(); + // miette::set_panic_hook(); let miette_hook = std::panic::take_hook(); std::panic::set_hook(Box::new(move |x| { crossterm::terminal::disable_raw_mode().unwrap(); @@ -126,6 +126,16 @@ fn main() -> Result<()> { stack.env_vars.insert(k, v); } + // Set up our initial config to start from + stack.vars.insert( + CONFIG_VARIABLE_ID, + Value::Record { + cols: vec![], + vals: vec![], + span: Span::unknown(), + }, + ); + match eval_block( &engine_state, &mut stack, @@ -133,7 +143,8 @@ fn main() -> Result<()> { PipelineData::new(Span::unknown()), ) { Ok(pipeline_data) => { - println!("{}", pipeline_data.collect_string("\n")); + let config = stack.get_config()?; + println!("{}", pipeline_data.collect_string("\n", &config)); } Err(err) => { let working_set = StateWorkingSet::new(&engine_state); @@ -159,6 +170,16 @@ fn main() -> Result<()> { stack.env_vars.insert(k, v); } + // Set up our initial config to start from + stack.vars.insert( + CONFIG_VARIABLE_ID, + Value::Record { + cols: vec![], + vals: vec![], + span: Span::unknown(), + }, + ); + // Load config startup file if let Some(mut config_path) = nu_path::config_dir() { config_path.push("nushell"); @@ -261,21 +282,27 @@ fn main() -> Result<()> { } } -fn print_value(value: Value, engine_state: &EngineState) -> Result<(), ShellError> { +fn print_value( + value: Value, + engine_state: &EngineState, + stack: &mut Stack, +) -> Result<(), ShellError> { // If the table function is in the declarations, then we can use it // to create the table value that will be printed in the terminal + + let config = stack.get_config()?; + let output = match engine_state.find_decl("table".as_bytes()) { Some(decl_id) => { - let mut stack = Stack::new(); let table = engine_state.get_decl(decl_id).run( engine_state, - &mut stack, + stack, &Call::new(), value.into_pipeline_data(), )?; - table.collect_string("\n") + table.collect_string("\n", &config) } - None => value.into_string(", "), + None => value.into_string(", ", &config), }; let stdout = std::io::stdout(); @@ -323,7 +350,10 @@ fn update_prompt<'prompt>( &block, PipelineData::new(Span::unknown()), ) { - Ok(pipeline_data) => pipeline_data.collect_string(""), + Ok(pipeline_data) => { + let config = stack.get_config().unwrap_or_default(); + pipeline_data.collect_string("", &config) + } Err(err) => { let working_set = StateWorkingSet::new(engine_state); report_error(&working_set, &err); @@ -366,7 +396,11 @@ fn eval_source( PipelineData::new(Span::unknown()), ) { Ok(pipeline_data) => { - if let Err(err) = print_value(pipeline_data.into_value(Span::unknown()), engine_state) { + if let Err(err) = print_value( + pipeline_data.into_value(Span::unknown()), + engine_state, + stack, + ) { let working_set = StateWorkingSet::new(engine_state); report_error(&working_set, &err); diff --git a/src/tests.rs b/src/tests.rs index 0bbcaa5f50..bc7355ecd7 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -899,3 +899,21 @@ fn record_1() -> TestResult { fn record_2() -> TestResult { run_test(r#"{'b': 'c'}.b"#, "c") } + +#[test] +fn config_var_1() -> TestResult { + // Note: this tests both the config variable and that it is properly captured into a block + run_test( + r#"let config = {"filesize_metric": $true }; do { 40kb | into string } "#, + "39.1 KiB", + ) +} + +#[test] +fn config_var_2() -> TestResult { + // Note: this tests both the config variable and that it is properly captured into a block + run_test( + r#"let config = {"filesize_metric": $false }; do { 40kb | into string } "#, + "40.0 KB", + ) +}