Add a config variable with engine support (#332)

* Add a config variable with engine support

* Add a config variable with engine support

* Oops, cleanup
This commit is contained in:
JT 2021-11-15 08:25:57 +13:00 committed by GitHub
parent e76451866d
commit 0f107b2830
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 333 additions and 96 deletions

View File

@ -35,7 +35,7 @@
- [x] ctrl-c support - [x] ctrl-c support
- [x] operator overflow - [x] operator overflow
- [x] Support for `$in` - [x] Support for `$in`
- [ ] config system - [x] config system
- [ ] shells - [ ] shells
- [ ] plugins - [ ] plugins
- [ ] dataframes - [ ] dataframes

View File

@ -2,7 +2,7 @@ use nu_engine::CallExt;
use nu_protocol::{ use nu_protocol::{
ast::{Call, CellPath}, ast::{Call, CellPath},
engine::{Command, EngineState, Stack}, 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) // 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 head = call.head;
let decimals_value: Option<i64> = call.get_flag(engine_state, stack, "decimals")?; let decimals_value: Option<i64> = call.get_flag(engine_state, stack, "decimals")?;
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?; 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() { if decimals && decimals_value.is_some() && decimals_value.unwrap().is_negative() {
return Err(ShellError::UnsupportedInput( return Err(ShellError::UnsupportedInput(
@ -147,13 +148,16 @@ fn string_helper(
input.map( input.map(
move |v| { move |v| {
if column_paths.is_empty() { if column_paths.is_empty() {
action(&v, head, decimals, decimals_value, false) action(&v, head, decimals, decimals_value, false, &config)
} else { } else {
let mut ret = v; let mut ret = v;
for path in &column_paths { for path in &column_paths {
let config = config.clone();
let r = ret.update_cell_path( let r = ret.update_cell_path(
&path.members, &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 { if let Err(error) = r {
return Value::Error { error }; return Value::Error { error };
@ -173,6 +177,7 @@ pub fn action(
decimals: bool, decimals: bool,
digits: Option<i64>, digits: Option<i64>,
group_digits: bool, group_digits: bool,
config: &Config,
) -> Value { ) -> Value {
match input { match input {
Value::Int { val, .. } => { Value::Int { val, .. } => {
@ -212,7 +217,7 @@ pub fn action(
}, },
Value::Filesize { val: _, .. } => Value::String { Value::Filesize { val: _, .. } => Value::String {
val: input.clone().into_string(", "), val: input.clone().into_string(", ", config),
span, span,
}, },
Value::Nothing { .. } => Value::String { Value::Nothing { .. } => Value::String {

View File

@ -2,7 +2,7 @@ use nu_engine::eval_block;
use nu_parser::parse; use nu_parser::parse;
use nu_protocol::{ use nu_protocol::{
engine::{Command, EngineState, Stack, StateWorkingSet}, engine::{Command, EngineState, Stack, StateWorkingSet},
PipelineData, Span, PipelineData, Span, Value, CONFIG_VARIABLE_ID,
}; };
use crate::To; use crate::To;
@ -57,6 +57,16 @@ pub fn test_examples(cmd: impl Command + 'static) {
let mut stack = Stack::new(); 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( match eval_block(
&engine_state, &engine_state,
&mut stack, &mut stack,

View File

@ -78,6 +78,7 @@ fn from_csv(
let noheaders = call.has_flag("noheaders"); let noheaders = call.has_flag("noheaders");
let separator: Option<Value> = call.get_flag(engine_state, stack, "separator")?; let separator: Option<Value> = call.get_flag(engine_state, stack, "separator")?;
let config = stack.get_config()?;
let sep = match separator { let sep = match separator {
Some(Value::String { val: s, span }) => { 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)] #[cfg(test)]

View File

@ -1,5 +1,5 @@
use csv::ReaderBuilder; 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( fn from_delimited_string_to_value(
s: String, s: String,
@ -50,8 +50,9 @@ pub fn from_delimited_data(
sep: char, sep: char,
input: PipelineData, input: PipelineData,
name: Span, name: Span,
config: &Config,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let concat_string = input.collect_string(""); let concat_string = input.collect_string("", config);
Ok( Ok(
from_delimited_string_to_value(concat_string, noheaders, sep, name) from_delimited_string_to_value(concat_string, noheaders, sep, name)

View File

@ -4,6 +4,7 @@ use indexmap::map::IndexMap;
use nu_engine::CallExt; use nu_engine::CallExt;
use nu_protocol::ast::Call; use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::Config;
use nu_protocol::{ use nu_protocol::{
Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value,
}; };
@ -41,7 +42,8 @@ impl Command for FromEml {
let head = call.head; let head = call.head;
let preview_body: Option<Spanned<i64>> = let preview_body: Option<Spanned<i64>> =
call.get_flag(engine_state, stack, "preview-body")?; 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> { fn examples(&self) -> Vec<Example> {
@ -176,8 +178,9 @@ fn from_eml(
input: PipelineData, input: PipelineData,
preview_body: Option<Spanned<i64>>, preview_body: Option<Spanned<i64>>,
head: Span, head: Span,
config: &Config,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let value = input.collect_string(""); let value = input.collect_string("", config);
let body_preview = preview_body let body_preview = preview_body
.map(|b| b.item as usize) .map(|b| b.item as usize)

View File

@ -72,12 +72,13 @@ impl Command for FromJson {
fn run( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,
_stack: &mut Stack, stack: &mut Stack,
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<nu_protocol::PipelineData, ShellError> { ) -> Result<nu_protocol::PipelineData, ShellError> {
let span = call.head; 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'); string_input.push('\n');
// TODO: turn this into a structured underline of the nu_json error // TODO: turn this into a structured underline of the nu_json error

View File

@ -2,7 +2,7 @@ use super::delimited::from_delimited_data;
use nu_protocol::ast::Call; use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{PipelineData, ShellError, Signature}; use nu_protocol::{Config, PipelineData, ShellError, Signature};
#[derive(Clone)] #[derive(Clone)]
pub struct FromTsv; pub struct FromTsv;
@ -27,20 +27,21 @@ impl Command for FromTsv {
fn run( fn run(
&self, &self,
_engine_state: &EngineState, _engine_state: &EngineState,
_stack: &mut Stack, stack: &mut Stack,
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<nu_protocol::PipelineData, ShellError> { ) -> 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 name = call.head;
let noheaders = call.has_flag("noheaders"); let noheaders = call.has_flag("noheaders");
from_delimited_data(noheaders, '\t', input, name) from_delimited_data(noheaders, '\t', input, name, config)
} }
#[cfg(test)] #[cfg(test)]

View File

@ -1,6 +1,6 @@
use nu_protocol::ast::Call; use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack}; 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)] #[derive(Clone)]
pub struct FromUrl; pub struct FromUrl;
@ -21,12 +21,13 @@ impl Command for FromUrl {
fn run( fn run(
&self, &self,
_engine_state: &EngineState, _engine_state: &EngineState,
_stack: &mut Stack, stack: &mut Stack,
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<nu_protocol::PipelineData, ShellError> { ) -> Result<nu_protocol::PipelineData, ShellError> {
let head = call.head; let head = call.head;
from_url(input, head) let config = stack.get_config()?;
from_url(input, head, &config)
} }
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
@ -52,8 +53,8 @@ impl Command for FromUrl {
} }
} }
fn from_url(input: PipelineData, head: Span) -> Result<PipelineData, ShellError> { fn from_url(input: PipelineData, head: Span, config: &Config) -> Result<PipelineData, ShellError> {
let concat_string = input.collect_string(""); let concat_string = input.collect_string("", config);
let result = serde_urlencoded::from_str::<Vec<(String, String)>>(&concat_string); let result = serde_urlencoded::from_str::<Vec<(String, String)>>(&concat_string);

View File

@ -2,7 +2,7 @@ use itertools::Itertools;
use nu_protocol::ast::Call; use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{ 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 serde::de::Deserialize;
use std::collections::HashMap; use std::collections::HashMap;
@ -65,12 +65,13 @@ impl Command for FromYaml {
fn run( fn run(
&self, &self,
_engine_state: &EngineState, _engine_state: &EngineState,
_stack: &mut Stack, stack: &mut Stack,
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<nu_protocol::PipelineData, ShellError> { ) -> Result<nu_protocol::PipelineData, ShellError> {
let head = call.head; 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( fn run(
&self, &self,
_engine_state: &EngineState, _engine_state: &EngineState,
_stack: &mut Stack, stack: &mut Stack,
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<nu_protocol::PipelineData, ShellError> { ) -> Result<nu_protocol::PipelineData, ShellError> {
let head = call.head; 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> { fn from_yaml(input: PipelineData, head: Span, config: &Config) -> Result<PipelineData, ShellError> {
let concat_string = input.collect_string(""); let concat_string = input.collect_string("", config);
match from_yaml_string_to_value(concat_string, head) { match from_yaml_string_to_value(concat_string, head) {
Ok(x) => Ok(x.into_pipeline_data()), Ok(x) => Ok(x.into_pipeline_data()),
@ -248,6 +250,7 @@ mod test {
}), }),
}, },
]; ];
let config = Config::default();
for tc in tt { for tc in tt {
let actual = from_yaml_string_to_value(tc.input.to_owned(), Span::unknown()); let actual = from_yaml_string_to_value(tc.input.to_owned(), Span::unknown());
if actual.is_err() { if actual.is_err() {
@ -259,8 +262,8 @@ mod test {
); );
} else { } else {
assert_eq!( assert_eq!(
actual.unwrap().into_string(""), actual.unwrap().into_string("", &config),
tc.expected.unwrap().into_string("") tc.expected.unwrap().into_string("", &config)
); );
} }
} }

View File

@ -49,10 +49,13 @@ impl Command for BuildString {
call: &Call, call: &Call,
_input: PipelineData, _input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> { ) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let config = stack.get_config()?;
let output = call let output = call
.positional .positional
.iter() .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>>()?; .collect::<Result<Vec<String>, ShellError>>()?;
Ok(Value::String { Ok(Value::String {

View File

@ -34,12 +34,14 @@ impl Command for StrCollect {
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let separator: Option<String> = call.opt(engine_state, stack, 0)?; 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 // Hmm, not sure what we actually want. If you don't use debug_string, Date comes out as human readable
// which feels funny // which feels funny
#[allow(clippy::needless_collect)] #[allow(clippy::needless_collect)]
let strings: Vec<String> = input let strings: Vec<String> = input
.into_iter() .into_iter()
.map(|value| value.debug_string("\n")) .map(|value| value.debug_string("\n", &config))
.collect(); .collect();
let output = if let Some(separator) = separator { let output = if let Some(separator) = separator {

View File

@ -8,7 +8,7 @@ use std::sync::mpsc;
use nu_protocol::engine::{EngineState, Stack}; use nu_protocol::engine::{EngineState, Stack};
use nu_protocol::{ast::Call, engine::Command, ShellError, Signature, SyntaxShape, Value}; 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; use nu_engine::CallExt;
@ -44,13 +44,15 @@ impl Command for External {
let last_expression = call.has_flag("last_expression"); let last_expression = call.has_flag("last_expression");
let env_vars = stack.get_env_vars(); let env_vars = stack.get_env_vars();
let config = stack.get_config()?;
let command = ExternalCommand { let command = ExternalCommand {
name, name,
args, args,
last_expression, last_expression,
env_vars, env_vars,
}; };
command.run_with_input(engine_state, input) command.run_with_input(engine_state, input, config)
} }
} }
@ -66,6 +68,7 @@ impl ExternalCommand {
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,
input: PipelineData, input: PipelineData,
config: Config,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let mut process = self.create_command(); let mut process = self.create_command();
@ -112,7 +115,10 @@ impl ExternalCommand {
} }
} }
x => { 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(()); return Err(());
} }
} }

View File

@ -3,7 +3,7 @@ use nu_engine::CallExt;
use nu_protocol::{ use nu_protocol::{
ast::{Call, PathMember}, ast::{Call, PathMember},
engine::{Command, EngineState, Stack}, 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 nu_term_grid::grid::{Alignment, Cell, Direction, Filling, Grid, GridOptions};
use terminal_size::{Height, Width}; use terminal_size::{Height, Width};
@ -57,10 +57,12 @@ prints out the list properly."#
let color_param: bool = call.has_flag("color"); let color_param: bool = call.has_flag("color");
let separator_param: Option<String> = call.get_flag(engine_state, stack, "separator")?; let separator_param: Option<String> = call.get_flag(engine_state, stack, "separator")?;
let config = stack.get_config()?;
match input { match input {
PipelineData::Value(Value::List { vals, .. }) => { PipelineData::Value(Value::List { vals, .. }) => {
// dbg!("value::list"); // dbg!("value::list");
let data = convert_to_list2(vals); let data = convert_to_list2(vals, &config);
if let Some(items) = data { if let Some(items) = data {
Ok(create_grid_output2( Ok(create_grid_output2(
items, items,
@ -75,7 +77,7 @@ prints out the list properly."#
} }
PipelineData::Stream(stream) => { PipelineData::Stream(stream) => {
// dbg!("value::stream"); // dbg!("value::stream");
let data = convert_to_list2(stream); let data = convert_to_list2(stream, &config);
if let Some(items) = data { if let Some(items) = data {
Ok(create_grid_output2( Ok(create_grid_output2(
items, items,
@ -94,7 +96,7 @@ prints out the list properly."#
let mut items = vec![]; let mut items = vec![];
for (i, (c, v)) in cols.into_iter().zip(vals.into_iter()).enumerate() { 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( Ok(create_grid_output2(
@ -171,7 +173,10 @@ fn create_grid_output2(
.into_pipeline_data() .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(); let mut iter = iter.into_iter().peekable();
if let Some(first) = iter.peek() { 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()]; let mut row = vec![row_num.to_string()];
if headers.is_empty() { if headers.is_empty() {
row.push(item.into_string(", ")) row.push(item.into_string(", ", config))
} else { } else {
for header in headers.iter().skip(1) { for header in headers.iter().skip(1) {
let result = match item { let result = match item {
@ -201,7 +206,7 @@ fn convert_to_list2(iter: impl IntoIterator<Item = Value>) -> Option<Vec<(usize,
}; };
match result { match result {
Ok(value) => row.push(value.into_string(", ")), Ok(value) => row.push(value.into_string(", ", config)),
Err(_) => row.push(String::new()), Err(_) => row.push(String::new()),
} }
} }

View File

@ -1,7 +1,7 @@
use nu_protocol::ast::{Call, PathMember}; use nu_protocol::ast::{Call, PathMember};
use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{IntoPipelineData, PipelineData, ShellError, Signature, Span, Value}; use nu_protocol::{Config, IntoPipelineData, PipelineData, ShellError, Signature, Span, Value};
use nu_table::StyledString; use nu_table::{StyledString, Theme};
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc; use std::sync::Arc;
@ -27,11 +27,12 @@ impl Command for Table {
fn run( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,
_stack: &mut Stack, stack: &mut Stack,
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> { ) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let ctrlc = engine_state.ctrlc.clone(); 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() { let term_width = if let Some((Width(w), Height(_h))) = terminal_size::terminal_size() {
w as usize w as usize
@ -41,7 +42,7 @@ impl Command for Table {
match input { match input {
PipelineData::Value(Value::List { vals, .. }) => { 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 { if let Some(table) = table {
let result = nu_table::draw_table(&table, term_width, &HashMap::new()); let result = nu_table::draw_table(&table, term_width, &HashMap::new());
@ -56,7 +57,7 @@ impl Command for Table {
} }
} }
PipelineData::Stream(stream) => { PipelineData::Stream(stream) => {
let table = convert_to_table(stream, ctrlc)?; let table = convert_to_table(stream, ctrlc, &config)?;
if let Some(table) = table { if let Some(table) = table {
let result = nu_table::draw_table(&table, term_width, &HashMap::new()); 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(), style: nu_table::TextStyle::default_field(),
}, },
StyledString { StyledString {
contents: v.into_string(", "), contents: v.into_string(", ", &config),
style: nu_table::TextStyle::default(), style: nu_table::TextStyle::default(),
}, },
]) ])
@ -89,7 +90,7 @@ impl Command for Table {
let table = nu_table::Table { let table = nu_table::Table {
headers: vec![], headers: vec![],
data: output, data: output,
theme: nu_table::Theme::rounded(), theme: load_theme_from_config(&config),
}; };
let result = nu_table::draw_table(&table, term_width, &HashMap::new()); let result = nu_table::draw_table(&table, term_width, &HashMap::new());
@ -109,6 +110,7 @@ impl Command for Table {
fn convert_to_table( fn convert_to_table(
iter: impl IntoIterator<Item = Value>, iter: impl IntoIterator<Item = Value>,
ctrlc: Option<Arc<AtomicBool>>, ctrlc: Option<Arc<AtomicBool>>,
config: &Config,
) -> Result<Option<nu_table::Table>, ShellError> { ) -> Result<Option<nu_table::Table>, ShellError> {
let mut iter = iter.into_iter().peekable(); let mut iter = iter.into_iter().peekable();
@ -133,7 +135,7 @@ fn convert_to_table(
let mut row = vec![row_num.to_string()]; let mut row = vec![row_num.to_string()];
if headers.is_empty() { if headers.is_empty() {
row.push(item.into_string(", ")) row.push(item.into_string(", ", config))
} else { } else {
for header in headers.iter().skip(1) { for header in headers.iter().skip(1) {
let result = match item { let result = match item {
@ -147,7 +149,7 @@ fn convert_to_table(
}; };
match result { match result {
Ok(value) => row.push(value.into_string(", ")), Ok(value) => row.push(value.into_string(", ", config)),
Err(_) => row.push(String::new()), Err(_) => row.push(String::new()),
} }
} }
@ -185,9 +187,24 @@ fn convert_to_table(
.collect::<Vec<StyledString>>() .collect::<Vec<StyledString>>()
}) })
.collect(), .collect(),
theme: nu_table::Theme::rounded(), theme: load_theme_from_config(config),
})) }))
} else { } else {
Ok(None) 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(),
}
}

View File

@ -429,7 +429,9 @@ pub fn eval_subexpression(
// to be used later // to be used later
// FIXME: the trimming of the end probably needs to live in a better place // 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') { if s.ends_with('\n') {
s.pop(); s.pop();
} }

View File

@ -95,14 +95,32 @@ impl FromValue for f64 {
impl FromValue for String { impl FromValue for String {
fn from_value(v: &Value) -> Result<Self, ShellError> { fn from_value(v: &Value) -> Result<Self, ShellError> {
// FIXME: we may want to fail a little nicer here // 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> { impl FromValue for Spanned<String> {
fn from_value(v: &Value) -> Result<Self, ShellError> { fn from_value(v: &Value) -> Result<Self, ShellError> {
Ok(Spanned { 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()?, span: v.span()?,
}) })
} }

View File

@ -1,7 +1,7 @@
use nu_protocol::{ use nu_protocol::{
ast::{Block, Call, Expr, Expression, ImportPattern, ImportPatternMember, Pipeline, Statement}, ast::{Block, Call, Expr, Expression, ImportPattern, ImportPatternMember, Pipeline, Statement},
engine::StateWorkingSet, engine::StateWorkingSet,
span, DeclId, Span, SyntaxShape, Type, span, DeclId, Span, SyntaxShape, Type, CONFIG_VARIABLE_ID,
}; };
use std::path::Path; use std::path::Path;
@ -800,8 +800,10 @@ pub fn parse_let(
.expect("internal error: expected variable"); .expect("internal error: expected variable");
let rhs_type = call.positional[1].ty.clone(); let rhs_type = call.positional[1].ty.clone();
if var_id != CONFIG_VARIABLE_ID {
working_set.set_variable_type(var_id, rhs_type); working_set.set_variable_type(var_id, rhs_type);
} }
}
return ( return (
Statement::Pipeline(Pipeline::from_vec(vec![Expression { Statement::Pipeline(Pipeline::from_vec(vec![Expression {

View File

@ -12,6 +12,7 @@ use nu_protocol::{
}, },
engine::StateWorkingSet, engine::StateWorkingSet,
span, Flag, PositionalArg, Signature, Span, Spanned, SyntaxShape, Type, Unit, VarId, span, Flag, PositionalArg, Signature, Span, Spanned, SyntaxShape, Type, Unit, VarId,
CONFIG_VARIABLE_ID,
}; };
use crate::parse_keywords::{ use crate::parse_keywords::{
@ -1201,6 +1202,16 @@ pub fn parse_variable_expr(
}, },
None, 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); 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])), 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 { } else {
let id = working_set.add_variable(bytes, Type::Unknown); let id = working_set.add_variable(bytes, Type::Unknown);

View File

@ -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)
}
}

View File

@ -136,13 +136,14 @@ pub struct EngineState {
pub const NU_VARIABLE_ID: usize = 0; pub const NU_VARIABLE_ID: usize = 0;
pub const SCOPE_VARIABLE_ID: usize = 1; pub const SCOPE_VARIABLE_ID: usize = 1;
pub const IN_VARIABLE_ID: usize = 2; pub const IN_VARIABLE_ID: usize = 2;
pub const CONFIG_VARIABLE_ID: usize = 3;
impl EngineState { impl EngineState {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
files: im::vector![], files: im::vector![],
file_contents: 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![], decls: im::vector![],
blocks: im::vector![], blocks: im::vector![],
scope: im::vector![ScopeFrame::new()], scope: im::vector![ScopeFrame::new()],

View File

@ -1,6 +1,6 @@
use std::collections::HashMap; 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 /// A runtime value stack used during evaluation
/// ///
@ -42,6 +42,7 @@ impl Stack {
if let Some(v) = self.vars.get(&var_id) { if let Some(v) = self.vars.get(&var_id) {
return Ok(v.clone()); return Ok(v.clone());
} }
Err(ShellError::InternalError("variable not found".into())) Err(ShellError::InternalError("variable not found".into()))
} }
@ -67,6 +68,11 @@ impl Stack {
// FIXME: this is probably slow // FIXME: this is probably slow
output.env_vars = self.env_vars.clone(); 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 output
} }
@ -81,6 +87,18 @@ impl Stack {
None 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) { pub fn print_stack(&self) {
println!("vars:"); println!("vars:");
for (var, val) in &self.vars { for (var, val) in &self.vars {

View File

@ -1,4 +1,5 @@
pub mod ast; pub mod ast;
mod config;
pub mod engine; pub mod engine;
mod example; mod example;
mod id; mod id;
@ -11,7 +12,8 @@ mod ty;
mod value; mod value;
pub use value::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 example::*;
pub use id::*; pub use id::*;
pub use pipeline_data::*; pub use pipeline_data::*;

View File

@ -1,6 +1,6 @@
use std::sync::{atomic::AtomicBool, Arc}; 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 /// 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 { match self {
PipelineData::Value(v) => v.into_string(separator), PipelineData::Value(v) => v.into_string(separator, config),
PipelineData::Stream(s) => s.into_string(separator), PipelineData::Stream(s) => s.into_string(separator, config),
} }
} }

View File

@ -80,7 +80,7 @@ pub enum ShellError {
#[diagnostic(code(nu::shell::internal_error), url(docsrs))] #[diagnostic(code(nu::shell::internal_error), url(docsrs))]
InternalError(String), InternalError(String),
#[error("Variable not found")] #[error("Variable not found!!!")]
#[diagnostic(code(nu::shell::variable_not_found), url(docsrs))] #[diagnostic(code(nu::shell::variable_not_found), url(docsrs))]
VariableNotFoundAtRuntime(#[label = "variable not found"] Span), VariableNotFoundAtRuntime(#[label = "variable not found"] Span),

View File

@ -15,7 +15,7 @@ use std::collections::HashMap;
use std::{cmp::Ordering, fmt::Debug}; use std::{cmp::Ordering, fmt::Debug};
use crate::ast::{CellPath, PathMember}; 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; 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 /// Get the span for the current value
pub fn span(&self) -> Result<Span, ShellError> { pub fn span(&self) -> Result<Span, ShellError> {
match self { match self {
@ -174,26 +196,26 @@ impl Value {
} }
/// Convert Value into string. Note that Streams will be consumed. /// 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 { match self {
Value::Bool { val, .. } => val.to_string(), Value::Bool { val, .. } => val.to_string(),
Value::Int { val, .. } => val.to_string(), Value::Int { val, .. } => val.to_string(),
Value::Float { 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::Duration { val, .. } => format_duration(val),
Value::Date { val, .. } => HumanTime::from(val).to_string(), Value::Date { val, .. } => HumanTime::from(val).to_string(),
Value::Range { val, .. } => { Value::Range { val, .. } => {
format!( format!(
"{}..{}", "{}..{}",
val.from.into_string(", "), val.from.into_string(", ", config),
val.to.into_string(", ") val.to.into_string(", ", config)
) )
} }
Value::String { val, .. } => val, Value::String { val, .. } => val,
Value::List { vals: val, .. } => format!( Value::List { vals: val, .. } => format!(
"[{}]", "[{}]",
val.into_iter() val.into_iter()
.map(|x| x.into_string(", ")) .map(|x| x.into_string(", ", config))
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join(separator) .join(separator)
), ),
@ -201,7 +223,7 @@ impl Value {
"{{{}}}", "{{{}}}",
cols.iter() cols.iter()
.zip(vals.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<_>>() .collect::<Vec<_>>()
.join(separator) .join(separator)
), ),
@ -214,26 +236,26 @@ impl Value {
} }
/// Convert Value into string. Note that Streams will be consumed. /// 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 { match self {
Value::Bool { val, .. } => val.to_string(), Value::Bool { val, .. } => val.to_string(),
Value::Int { val, .. } => val.to_string(), Value::Int { val, .. } => val.to_string(),
Value::Float { 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::Duration { val, .. } => format_duration(val),
Value::Date { val, .. } => format!("{:?}", val), Value::Date { val, .. } => format!("{:?}", val),
Value::Range { val, .. } => { Value::Range { val, .. } => {
format!( format!(
"{}..{}", "{}..{}",
val.from.into_string(", "), val.from.into_string(", ", config),
val.to.into_string(", ") val.to.into_string(", ", config)
) )
} }
Value::String { val, .. } => val, Value::String { val, .. } => val,
Value::List { vals: val, .. } => format!( Value::List { vals: val, .. } => format!(
"[{}]", "[{}]",
val.into_iter() val.into_iter()
.map(|x| x.into_string(", ")) .map(|x| x.into_string(", ", config))
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join(separator) .join(separator)
), ),
@ -241,7 +263,7 @@ impl Value {
"{{{}}}", "{{{}}}",
cols.iter() cols.iter()
.zip(vals.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<_>>() .collect::<Vec<_>>()
.join(separator) .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); let byte = byte_unit::Byte::from_bytes(num_bytes as u128);
if byte.get_bytes() == 0u128 { if byte.get_bytes() == 0u128 {
return "".to_string(); return "".to_string();
} }
let byte = byte.get_appropriate_unit(false); let byte = byte.get_appropriate_unit(config.filesize_metric);
match byte.get_unit() { match byte.get_unit() {
byte_unit::ByteUnit::B => format!("{} B ", byte.get_value()), byte_unit::ByteUnit::B => format!("{} B ", byte.get_value()),

View File

@ -6,7 +6,7 @@ use crate::*;
pub struct RowStream(Rc<RefCell<dyn Iterator<Item = Vec<Value>>>>); pub struct RowStream(Rc<RefCell<dyn Iterator<Item = Vec<Value>>>>);
impl RowStream { impl RowStream {
pub fn into_string(self, headers: Vec<String>) -> String { pub fn into_string(self, headers: Vec<String>, config: &Config) -> String {
format!( format!(
"[{}]\n[{}]", "[{}]\n[{}]",
headers headers
@ -16,7 +16,7 @@ impl RowStream {
.join(", "), .join(", "),
self.map(|x: Vec<Value>| { self.map(|x: Vec<Value>| {
x.into_iter() x.into_iter()
.map(|x| x.into_string(", ")) .map(|x| x.into_string(", ", config))
.collect::<Vec<String>>() .collect::<Vec<String>>()
.join(", ") .join(", ")
}) })

View File

@ -19,8 +19,8 @@ pub struct ValueStream {
} }
impl ValueStream { impl ValueStream {
pub fn into_string(self, separator: &str) -> String { pub fn into_string(self, separator: &str, config: &Config) -> String {
self.map(|x: Value| x.into_string(", ")) self.map(|x: Value| x.into_string(", ", config))
.collect::<Vec<String>>() .collect::<Vec<String>>()
.join(separator) .join(separator)
} }

View File

@ -19,7 +19,7 @@ use nu_parser::parse;
use nu_protocol::{ use nu_protocol::{
ast::Call, ast::Call,
engine::{EngineState, Stack, StateWorkingSet}, engine::{EngineState, Stack, StateWorkingSet},
IntoPipelineData, PipelineData, ShellError, Span, Value, IntoPipelineData, PipelineData, ShellError, Span, Value, CONFIG_VARIABLE_ID,
}; };
use reedline::{Completer, CompletionActionHandler, DefaultPrompt, LineBuffer, Prompt}; use reedline::{Completer, CompletionActionHandler, DefaultPrompt, LineBuffer, Prompt};
@ -81,7 +81,7 @@ impl CompletionActionHandler for FuzzyCompletion {
} }
fn main() -> Result<()> { fn main() -> Result<()> {
miette::set_panic_hook(); // miette::set_panic_hook();
let miette_hook = std::panic::take_hook(); let miette_hook = std::panic::take_hook();
std::panic::set_hook(Box::new(move |x| { std::panic::set_hook(Box::new(move |x| {
crossterm::terminal::disable_raw_mode().unwrap(); crossterm::terminal::disable_raw_mode().unwrap();
@ -126,6 +126,16 @@ fn main() -> Result<()> {
stack.env_vars.insert(k, v); 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( match eval_block(
&engine_state, &engine_state,
&mut stack, &mut stack,
@ -133,7 +143,8 @@ fn main() -> Result<()> {
PipelineData::new(Span::unknown()), PipelineData::new(Span::unknown()),
) { ) {
Ok(pipeline_data) => { Ok(pipeline_data) => {
println!("{}", pipeline_data.collect_string("\n")); let config = stack.get_config()?;
println!("{}", pipeline_data.collect_string("\n", &config));
} }
Err(err) => { Err(err) => {
let working_set = StateWorkingSet::new(&engine_state); let working_set = StateWorkingSet::new(&engine_state);
@ -159,6 +170,16 @@ fn main() -> Result<()> {
stack.env_vars.insert(k, v); 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 // Load config startup file
if let Some(mut config_path) = nu_path::config_dir() { if let Some(mut config_path) = nu_path::config_dir() {
config_path.push("nushell"); 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 // 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 // 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()) { let output = match engine_state.find_decl("table".as_bytes()) {
Some(decl_id) => { Some(decl_id) => {
let mut stack = Stack::new();
let table = engine_state.get_decl(decl_id).run( let table = engine_state.get_decl(decl_id).run(
engine_state, engine_state,
&mut stack, stack,
&Call::new(), &Call::new(),
value.into_pipeline_data(), 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(); let stdout = std::io::stdout();
@ -323,7 +350,10 @@ fn update_prompt<'prompt>(
&block, &block,
PipelineData::new(Span::unknown()), 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) => { Err(err) => {
let working_set = StateWorkingSet::new(engine_state); let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &err); report_error(&working_set, &err);
@ -366,7 +396,11 @@ fn eval_source(
PipelineData::new(Span::unknown()), PipelineData::new(Span::unknown()),
) { ) {
Ok(pipeline_data) => { 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); let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &err); report_error(&working_set, &err);

View File

@ -899,3 +899,21 @@ fn record_1() -> TestResult {
fn record_2() -> TestResult { fn record_2() -> TestResult {
run_test(r#"{'b': 'c'}.b"#, "c") 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",
)
}