Repository: nushell/engine-q
Pull request #332 (2021-11-13 19:26:32)

Add a config variable with engine support
This adds a new builtin variable `$config`. The `$config` variable allows users to update the config by setting this to a record with known fields.

I currently have only ported over a couple settings, but we can keep adding more.

My current config.nu:

```
let $config = {
  filesize_metric: $true
  table_mode: with_love
}
```

The `$config` variable follows all the scoping of normal variables and is assumed captured by blocks.


Patch:
diff --git a/TODO.md b/TODO.md
index d3dd766..22a708b 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 533f319..318d335 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<i64> = call.get_flag(engine_state, stack, "decimals")?;
     let column_paths: Vec<CellPath> = 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<i64>,
     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/default_context.rs b/crates/nu-command/src/default_context.rs
index d60c06a..3db8996 100644
--- a/crates/nu-command/src/default_context.rs
+++ b/crates/nu-command/src/default_context.rs
@@ -107,6 +107,7 @@ pub fn create_default_context() -> EngineState {
             StrDowncase,
             StrEndswith,
             StrIndexOf,
+            StrLength,
             StrFindReplace,
             StrKebabCase,
             StrPascalCase,
@@ -118,6 +119,7 @@ pub fn create_default_context() -> EngineState {
             ToJson,
             Touch,
             Use,
+            Update,
             Where,
             WithEnv,
             Wrap,
diff --git a/crates/nu-command/src/filters/mod.rs b/crates/nu-command/src/filters/mod.rs
index ea9495a..bf93145 100644
--- a/crates/nu-command/src/filters/mod.rs
+++ b/crates/nu-command/src/filters/mod.rs
@@ -10,6 +10,7 @@ mod range;
 mod reverse;
 mod select;
 mod shuffle;
+mod update;
 mod where_;
 mod wrap;
 mod zip;
@@ -26,6 +27,7 @@ pub use range::Range;
 pub use reverse::Reverse;
 pub use select::Select;
 pub use shuffle::Shuffle;
+pub use update::Update;
 pub use where_::Where;
 pub use wrap::Wrap;
 pub use zip::Zip;
diff --git a/crates/nu-command/src/filters/update.rs b/crates/nu-command/src/filters/update.rs
new file mode 100644
index 0000000..5e5b120
--- /dev/null
+++ b/crates/nu-command/src/filters/update.rs
@@ -0,0 +1,120 @@
+use nu_engine::{eval_block, CallExt};
+use nu_protocol::ast::{Call, CellPath};
+use nu_protocol::engine::{Command, EngineState, Stack};
+use nu_protocol::{
+    Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
+};
+
+#[derive(Clone)]
+pub struct Update;
+
+impl Command for Update {
+    fn name(&self) -> &str {
+        "update"
+    }
+
+    fn signature(&self) -> Signature {
+        Signature::build("update")
+            .required(
+                "field",
+                SyntaxShape::CellPath,
+                "the name of the column to update",
+            )
+            .required(
+                "replacement value",
+                SyntaxShape::Any,
+                "the new value to give the cell(s)",
+            )
+    }
+
+    fn usage(&self) -> &str {
+        "Update an existing column to have a new value."
+    }
+
+    fn run(
+        &self,
+        engine_state: &EngineState,
+        stack: &mut Stack,
+        call: &Call,
+        input: PipelineData,
+    ) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
+        update(engine_state, stack, call, input)
+    }
+
+    fn examples(&self) -> Vec<Example> {
+        vec![Example {
+            description: "Update a column value",
+            example: "echo {'name': 'nu', 'stars': 5} | update name 'Nushell'",
+            result: Some(Value::Record { cols: vec!["name".into(), "stars".into()], vals: vec![Value::test_string("Nushell"), Value::test_int(5)], span: Span::unknown()}),
+        }, Example {
+            description: "Use in block form for more involved updating logic",
+            example: "echo [[project, authors]; ['nu', ['Andrés', 'JT', 'Yehuda']]] | update authors { get authors | str collect ',' }",
+            result: Some(Value::List { vals: vec![Value::Record { cols: vec!["project".into(), "authors".into()], vals: vec![Value::test_string("nu"), Value::test_string("Andrés,JT,Yehuda")], span: Span::unknown()}], span: Span::unknown()}),
+        }]
+    }
+}
+
+fn update(
+    engine_state: &EngineState,
+    stack: &mut Stack,
+    call: &Call,
+    input: PipelineData,
+) -> Result<PipelineData, ShellError> {
+    let span = call.head;
+
+    let cell_path: CellPath = call.req(engine_state, stack, 0)?;
+    let replacement: Value = call.req(engine_state, stack, 1)?;
+    let engine_state = engine_state.clone();
+    let ctrlc = engine_state.ctrlc.clone();
+
+    // Replace is a block, so set it up and run it instead of using it as the replacement
+    if let Ok(block_id) = replacement.as_block() {
+        let block = engine_state.get_block(block_id).clone();
+
+        let mut stack = stack.collect_captures(&block.captures);
+
+        input.map(
+            move |mut input| {
+                if let Some(var) = block.signature.get_positional(0) {
+                    if let Some(var_id) = &var.var_id {
+                        stack.add_var(*var_id, input.clone())
+                    }
+                }
+
+                let output = eval_block(
+                    &engine_state,
+                    &mut stack,
+                    &block,
+                    input.clone().into_pipeline_data(),
+                );
+
+                match output {
+                    Ok(pd) => {
+                        if let Err(e) =
+                            input.replace_data_at_cell_path(&cell_path.members, pd.into_value(span))
+                        {
+                            return Value::Error { error: e };
+                        }
+
+                        input
+                    }
+                    Err(e) => Value::Error { error: e },
+                }
+            },
+            ctrlc,
+        )
+    } else {
+        input.map(
+            move |mut input| {
+                let replacement = replacement.clone();
+
+                if let Err(e) = input.replace_data_at_cell_path(&cell_path.members, replacement) {
+                    return Value::Error { error: e };
+                }
+
+                input
+            },
+            ctrlc,
+        )
+    }
+}
diff --git a/crates/nu-command/src/formats/from/csv.rs b/crates/nu-command/src/formats/from/csv.rs
index 25f8de5..02b3500 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<Value> = 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 4e17206..eafdd4e 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<PipelineData, ShellError> {
-    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 3c8b684..9ded63e 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<Spanned<i64>> =
             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<Example> {
@@ -176,8 +178,9 @@ fn from_eml(
     input: PipelineData,
     preview_body: Option<Spanned<i64>>,
     head: Span,
+    config: &Config,
 ) -> Result<PipelineData, ShellError> {
-    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 1cdbda8..75c6bcf 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<nu_protocol::PipelineData, ShellError> {
         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 2c07a29..4f9a2e7 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<nu_protocol::PipelineData, ShellError> {
-        from_tsv(call, input)
+        let config = stack.get_config()?;
+        from_tsv(call, input, &config)
     }
 }
 
-fn from_tsv(call: &Call, input: PipelineData) -> Result<PipelineData, ShellError> {
+fn from_tsv(call: &Call, input: PipelineData, config: &Config) -> Result<PipelineData, ShellError> {
     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 4df1fd9..bc113be 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<nu_protocol::PipelineData, ShellError> {
         let head = call.head;
-        from_url(input, head)
+        let config = stack.get_config()?;
+        from_url(input, head, &config)
     }
 
     fn examples(&self) -> Vec<Example> {
@@ -52,8 +53,8 @@ impl Command for FromUrl {
     }
 }
 
-fn from_url(input: PipelineData, head: Span) -> Result<PipelineData, ShellError> {
-    let concat_string = input.collect_string("");
+fn from_url(input: PipelineData, head: Span, config: &Config) -> Result<PipelineData, ShellError> {
+    let concat_string = input.collect_string("", config);
 
     let result = serde_urlencoded::from_str::<Vec<(String, String)>>(&concat_string);
 
diff --git a/crates/nu-command/src/formats/from/yaml.rs b/crates/nu-command/src/formats/from/yaml.rs
index 44ddca0..2114a84 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<nu_protocol::PipelineData, ShellError> {
         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<nu_protocol::PipelineData, ShellError> {
         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<Value, ShellEr
     }
 }
 
-fn from_yaml(input: PipelineData, head: Span) -> Result<PipelineData, ShellError> {
-    let concat_string = input.collect_string("");
+fn from_yaml(input: PipelineData, head: Span, config: &Config) -> Result<PipelineData, ShellError> {
+    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 27aa2e9..5c6692a 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<nu_protocol::PipelineData, nu_protocol::ShellError> {
+        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::<Result<Vec<String>, 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 4394dbe..e4b2fea 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<PipelineData, ShellError> {
         let separator: Option<String> = 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<String> = 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/strings/str_/length.rs b/crates/nu-command/src/strings/str_/length.rs
new file mode 100644
index 0000000..df082e9
--- /dev/null
+++ b/crates/nu-command/src/strings/str_/length.rs
@@ -0,0 +1,112 @@
+use nu_engine::CallExt;
+use nu_protocol::ast::Call;
+use nu_protocol::ast::CellPath;
+use nu_protocol::engine::{Command, EngineState, Stack};
+use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value};
+
+#[derive(Clone)]
+pub struct SubCommand;
+
+impl Command for SubCommand {
+    fn name(&self) -> &str {
+        "str length"
+    }
+
+    fn signature(&self) -> Signature {
+        Signature::build("str length").rest(
+            "rest",
+            SyntaxShape::CellPath,
+            "optionally find length of text by column paths",
+        )
+    }
+
+    fn usage(&self) -> &str {
+        "outputs the lengths of the strings in the pipeline"
+    }
+
+    fn run(
+        &self,
+        engine_state: &EngineState,
+        stack: &mut Stack,
+        call: &Call,
+        input: PipelineData,
+    ) -> Result<PipelineData, ShellError> {
+        operate(engine_state, stack, call, input)
+    }
+
+    fn examples(&self) -> Vec<Example> {
+        vec![
+            Example {
+                description: "Return the lengths of multiple strings",
+                example: "'hello' | str length",
+                result: Some(Value::test_int(5)),
+            },
+            Example {
+                description: "Return the lengths of multiple strings",
+                example: "['hi' 'there'] | str length",
+                result: Some(Value::List {
+                    vals: vec![Value::test_int(2), Value::test_int(5)],
+                    span: Span::unknown(),
+                }),
+            },
+        ]
+    }
+}
+
+fn operate(
+    engine_state: &EngineState,
+    stack: &mut Stack,
+    call: &Call,
+    input: PipelineData,
+) -> Result<PipelineData, ShellError> {
+    let head = call.head;
+    let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
+    input.map(
+        move |v| {
+            if column_paths.is_empty() {
+                action(&v, head)
+            } else {
+                let mut ret = v;
+                for path in &column_paths {
+                    let r =
+                        ret.update_cell_path(&path.members, Box::new(move |old| action(old, head)));
+                    if let Err(error) = r {
+                        return Value::Error { error };
+                    }
+                }
+                ret
+            }
+        },
+        engine_state.ctrlc.clone(),
+    )
+}
+
+fn action(input: &Value, head: Span) -> Value {
+    match input {
+        Value::String { val, .. } => Value::Int {
+            val: val.len() as i64,
+            span: head,
+        },
+        other => Value::Error {
+            error: ShellError::UnsupportedInput(
+                format!(
+                    "Input's type is {}. This command only works with strings.",
+                    other.get_type()
+                ),
+                Span::unknown(),
+            ),
+        },
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[test]
+    fn test_examples() {
+        use crate::test_examples;
+
+        test_examples(SubCommand {})
+    }
+}
diff --git a/crates/nu-command/src/strings/str_/mod.rs b/crates/nu-command/src/strings/str_/mod.rs
index ca656a1..18a5ada 100644
--- a/crates/nu-command/src/strings/str_/mod.rs
+++ b/crates/nu-command/src/strings/str_/mod.rs
@@ -6,6 +6,7 @@ mod downcase;
 mod ends_with;
 mod find_replace;
 mod index_of;
+mod length;
 
 pub use capitalize::SubCommand as StrCapitalize;
 pub use case::*;
@@ -15,3 +16,4 @@ pub use downcase::SubCommand as StrDowncase;
 pub use ends_with::SubCommand as StrEndswith;
 pub use find_replace::SubCommand as StrFindReplace;
 pub use index_of::SubCommand as StrIndexOf;
+pub use length::SubCommand as StrLength;
diff --git a/crates/nu-command/src/system/run_external.rs b/crates/nu-command/src/system/run_external.rs
index 183f253..d0dedae 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<PipelineData, ShellError> {
         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 9d9e3c9..e623156 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<String> = 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<Item = Value>) -> Option<Vec<(usize, String, String)>> {
+fn convert_to_list2(
+    iter: impl IntoIterator<Item = Value>,
+    config: &Config,
+) -> Option<Vec<(usize, String, String)>> {
     let mut iter = iter.into_iter().peekable();
 
     if let Some(first) = iter.peek() {
@@ -187,7 +192,7 @@ fn convert_to_list2(iter: impl IntoIterator<Item = Value>) -> Option<Vec<(usize,
             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 {
@@ -201,7 +206,7 @@ fn convert_to_list2(iter: impl IntoIterator<Item = Value>) -> Option<Vec<(usize,
                     };
 
                     match result {
-                        Ok(value) => 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 985a8bc..9a4a7c5 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<nu_protocol::PipelineData, nu_protocol::ShellError> {
         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<Item = Value>,
     ctrlc: Option<Arc<AtomicBool>>,
+    config: &Config,
 ) -> Result<Option<nu_table::Table>, 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::<Vec<StyledString>>()
                 })
                 .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 c411a2d..1dd4ad8 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 66c9317..281796a 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<Self, ShellError> {
         // 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<String> {
     fn from_value(v: &Value) -> Result<Self, ShellError> {
         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 2a138bb..030ef73 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 e413e28..3f958b3 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 0000000..ff9cb50
--- /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<Config, ShellError> {
+        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 cbe3548..3929357 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 f2fc9fc..cc3badb 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<Config, ShellError> {
+        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 bb1e775..89f6896 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 ba7a697..67f63ca 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 fdbca5e..54637cb 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 7724076..463777d 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<bool, ShellError> {
+        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<Span, ShellError> {
         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::<Vec<_>>()
                     .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::<Vec<_>>()
                     .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::<Vec<_>>()
                     .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::<Vec<_>>()
                     .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 462739d..231f8d8 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<RefCell<dyn Iterator<Item = Vec<Value>>>>);
 
 impl RowStream {
-    pub fn into_string(self, headers: Vec<String>) -> String {
+    pub fn into_string(self, headers: Vec<String>, config: &Config) -> String {
         format!(
             "[{}]\n[{}]",
             headers
@@ -16,7 +16,7 @@ impl RowStream {
                 .join(", "),
             self.map(|x: Vec<Value>| {
                 x.into_iter()
-                    .map(|x| x.into_string(", "))
+                    .map(|x| x.into_string(", ", config))
                     .collect::<Vec<String>>()
                     .join(", ")
             })
diff --git a/crates/nu-protocol/src/value/stream.rs b/crates/nu-protocol/src/value/stream.rs
index f49965a..728bd0f 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::<Vec<String>>()
             .join(separator)
     }
diff --git a/src/main.rs b/src/main.rs
index 10cf327..c54c9cc 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);


Test patch:
diff --git a/crates/nu-command/src/example_test.rs b/crates/nu-command/src/example_test.rs
index 7e1f390..b9e6f6d 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/src/tests.rs b/src/tests.rs
index 0bbcaa5..bc7355e 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",
+    )
+}