mirror of
https://github.com/nushell/nushell.git
synced 2024-11-07 09:04:18 +01:00
Refactor config updates (#13802)
# Description This PR standardizes updates to the config through a new `UpdateFromValue` trait. For now, this trait is private in case we need to make changes to it. Note that this PR adds some additional `ShellError` cases to create standard error messages for config errors. A follow-up PR will move usages of the old error cases to these new ones. This PR also uses `Type::custom` in lots of places (e.g., for string enums). Not sure if this is something we want to encourage. # User-Facing Changes Should be none.
This commit is contained in:
parent
02313e6819
commit
fce6146576
@ -5,11 +5,10 @@ use nu_color_config::{color_record_to_nustyle, lookup_ansi_color_style};
|
||||
use nu_engine::eval_block;
|
||||
use nu_parser::parse;
|
||||
use nu_protocol::{
|
||||
create_menus,
|
||||
debugger::WithoutDebug,
|
||||
engine::{EngineState, Stack, StateWorkingSet},
|
||||
extract_value, Config, EditBindings, ParsedKeybinding, ParsedMenu, PipelineData, Record,
|
||||
ShellError, Span, Value,
|
||||
extract_value, Config, EditBindings, FromValue, ParsedKeybinding, ParsedMenu, PipelineData,
|
||||
Record, ShellError, Span, Type, Value,
|
||||
};
|
||||
use reedline::{
|
||||
default_emacs_keybindings, default_vi_insert_keybindings, default_vi_normal_keybindings,
|
||||
@ -173,15 +172,13 @@ pub(crate) fn add_menus(
|
||||
|
||||
for res in menu_eval_results.into_iter() {
|
||||
if let PipelineData::Value(value, None) = res {
|
||||
for menu in create_menus(&value)? {
|
||||
line_editor = add_menu(
|
||||
line_editor,
|
||||
&menu,
|
||||
new_engine_state_ref.clone(),
|
||||
stack,
|
||||
config.clone(),
|
||||
)?;
|
||||
}
|
||||
line_editor = add_menu(
|
||||
line_editor,
|
||||
&ParsedMenu::from_value(value)?,
|
||||
new_engine_state_ref.clone(),
|
||||
stack,
|
||||
config.clone(),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
@ -204,22 +201,22 @@ fn add_menu(
|
||||
"list" => add_list_menu(line_editor, menu, engine_state, stack, config),
|
||||
"ide" => add_ide_menu(line_editor, menu, engine_state, stack, config),
|
||||
"description" => add_description_menu(line_editor, menu, engine_state, stack, config),
|
||||
_ => Err(ShellError::UnsupportedConfigValue {
|
||||
expected: "columnar, list, ide or description".to_string(),
|
||||
value: menu.r#type.to_abbreviated_string(&config),
|
||||
span: menu.r#type.span(),
|
||||
str => Err(ShellError::InvalidValue {
|
||||
valid: "'columnar', 'list', 'ide', or 'description'".into(),
|
||||
actual: format!("'{str}'"),
|
||||
span,
|
||||
}),
|
||||
}
|
||||
} else {
|
||||
Err(ShellError::UnsupportedConfigValue {
|
||||
expected: "only record type".to_string(),
|
||||
value: menu.r#type.to_abbreviated_string(&config),
|
||||
span: menu.r#type.span(),
|
||||
Err(ShellError::RuntimeTypeMismatch {
|
||||
expected: Type::record(),
|
||||
actual: menu.r#type.get_type(),
|
||||
span,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn get_style(record: &Record, name: &str, span: Span) -> Option<Style> {
|
||||
fn get_style(record: &Record, name: &'static str, span: Span) -> Option<Style> {
|
||||
extract_value(name, record, span)
|
||||
.ok()
|
||||
.map(|text| match text {
|
||||
@ -298,30 +295,23 @@ pub(crate) fn add_columnar_menu(
|
||||
let only_buffer_difference = menu.only_buffer_difference.as_bool()?;
|
||||
columnar_menu = columnar_menu.with_only_buffer_difference(only_buffer_difference);
|
||||
|
||||
let span = menu.source.span();
|
||||
match &menu.source {
|
||||
Value::Nothing { .. } => {
|
||||
Ok(line_editor.with_menu(ReedlineMenu::EngineCompleter(Box::new(columnar_menu))))
|
||||
}
|
||||
Value::Closure { val, .. } => {
|
||||
let menu_completer = NuMenuCompleter::new(
|
||||
val.block_id,
|
||||
span,
|
||||
stack.captures_to_stack(val.captures.clone()),
|
||||
engine_state,
|
||||
only_buffer_difference,
|
||||
);
|
||||
Ok(line_editor.with_menu(ReedlineMenu::WithCompleter {
|
||||
menu: Box::new(columnar_menu),
|
||||
completer: Box::new(menu_completer),
|
||||
}))
|
||||
}
|
||||
_ => Err(ShellError::UnsupportedConfigValue {
|
||||
expected: "block or omitted value".to_string(),
|
||||
value: menu.source.to_abbreviated_string(config),
|
||||
let completer = if let Some(closure) = &menu.source {
|
||||
let menu_completer = NuMenuCompleter::new(
|
||||
closure.block_id,
|
||||
span,
|
||||
}),
|
||||
}
|
||||
stack.captures_to_stack(closure.captures.clone()),
|
||||
engine_state,
|
||||
only_buffer_difference,
|
||||
);
|
||||
ReedlineMenu::WithCompleter {
|
||||
menu: Box::new(columnar_menu),
|
||||
completer: Box::new(menu_completer),
|
||||
}
|
||||
} else {
|
||||
ReedlineMenu::EngineCompleter(Box::new(columnar_menu))
|
||||
};
|
||||
|
||||
Ok(line_editor.with_menu(completer))
|
||||
}
|
||||
|
||||
// Adds a search menu to the line editor
|
||||
@ -354,30 +344,23 @@ pub(crate) fn add_list_menu(
|
||||
let only_buffer_difference = menu.only_buffer_difference.as_bool()?;
|
||||
list_menu = list_menu.with_only_buffer_difference(only_buffer_difference);
|
||||
|
||||
let span = menu.source.span();
|
||||
match &menu.source {
|
||||
Value::Nothing { .. } => {
|
||||
Ok(line_editor.with_menu(ReedlineMenu::HistoryMenu(Box::new(list_menu))))
|
||||
let completer = if let Some(closure) = &menu.source {
|
||||
let menu_completer = NuMenuCompleter::new(
|
||||
closure.block_id,
|
||||
span,
|
||||
stack.captures_to_stack(closure.captures.clone()),
|
||||
engine_state,
|
||||
only_buffer_difference,
|
||||
);
|
||||
ReedlineMenu::WithCompleter {
|
||||
menu: Box::new(list_menu),
|
||||
completer: Box::new(menu_completer),
|
||||
}
|
||||
Value::Closure { val, .. } => {
|
||||
let menu_completer = NuMenuCompleter::new(
|
||||
val.block_id,
|
||||
span,
|
||||
stack.captures_to_stack(val.captures.clone()),
|
||||
engine_state,
|
||||
only_buffer_difference,
|
||||
);
|
||||
Ok(line_editor.with_menu(ReedlineMenu::WithCompleter {
|
||||
menu: Box::new(list_menu),
|
||||
completer: Box::new(menu_completer),
|
||||
}))
|
||||
}
|
||||
_ => Err(ShellError::UnsupportedConfigValue {
|
||||
expected: "block or omitted value".to_string(),
|
||||
value: menu.source.to_abbreviated_string(&config),
|
||||
span: menu.source.span(),
|
||||
}),
|
||||
}
|
||||
} else {
|
||||
ReedlineMenu::HistoryMenu(Box::new(list_menu))
|
||||
};
|
||||
|
||||
Ok(line_editor.with_menu(completer))
|
||||
}
|
||||
|
||||
// Adds an IDE menu to the line editor
|
||||
@ -452,9 +435,9 @@ pub(crate) fn add_ide_menu(
|
||||
vertical,
|
||||
)
|
||||
} else {
|
||||
return Err(ShellError::UnsupportedConfigValue {
|
||||
expected: "bool or record".to_string(),
|
||||
value: border.to_abbreviated_string(&config),
|
||||
return Err(ShellError::RuntimeTypeMismatch {
|
||||
expected: Type::custom("bool or record"),
|
||||
actual: border.get_type(),
|
||||
span: border.span(),
|
||||
});
|
||||
}
|
||||
@ -475,10 +458,10 @@ pub(crate) fn add_ide_menu(
|
||||
"left" => ide_menu.with_description_mode(DescriptionMode::Left),
|
||||
"right" => ide_menu.with_description_mode(DescriptionMode::Right),
|
||||
"prefer_right" => ide_menu.with_description_mode(DescriptionMode::PreferRight),
|
||||
_ => {
|
||||
return Err(ShellError::UnsupportedConfigValue {
|
||||
expected: "\"left\", \"right\" or \"prefer_right\"".to_string(),
|
||||
value: description_mode.to_abbreviated_string(&config),
|
||||
str => {
|
||||
return Err(ShellError::InvalidValue {
|
||||
valid: "'left', 'right', or 'prefer_right'".into(),
|
||||
actual: format!("'{str}'"),
|
||||
span: description_mode.span(),
|
||||
});
|
||||
}
|
||||
@ -535,30 +518,23 @@ pub(crate) fn add_ide_menu(
|
||||
let only_buffer_difference = menu.only_buffer_difference.as_bool()?;
|
||||
ide_menu = ide_menu.with_only_buffer_difference(only_buffer_difference);
|
||||
|
||||
let span = menu.source.span();
|
||||
match &menu.source {
|
||||
Value::Nothing { .. } => {
|
||||
Ok(line_editor.with_menu(ReedlineMenu::EngineCompleter(Box::new(ide_menu))))
|
||||
}
|
||||
Value::Closure { val, .. } => {
|
||||
let menu_completer = NuMenuCompleter::new(
|
||||
val.block_id,
|
||||
span,
|
||||
stack.captures_to_stack(val.captures.clone()),
|
||||
engine_state,
|
||||
only_buffer_difference,
|
||||
);
|
||||
Ok(line_editor.with_menu(ReedlineMenu::WithCompleter {
|
||||
menu: Box::new(ide_menu),
|
||||
completer: Box::new(menu_completer),
|
||||
}))
|
||||
}
|
||||
_ => Err(ShellError::UnsupportedConfigValue {
|
||||
expected: "block or omitted value".to_string(),
|
||||
value: menu.source.to_abbreviated_string(&config),
|
||||
let completer = if let Some(closure) = &menu.source {
|
||||
let menu_completer = NuMenuCompleter::new(
|
||||
closure.block_id,
|
||||
span,
|
||||
}),
|
||||
}
|
||||
stack.captures_to_stack(closure.captures.clone()),
|
||||
engine_state,
|
||||
only_buffer_difference,
|
||||
);
|
||||
ReedlineMenu::WithCompleter {
|
||||
menu: Box::new(ide_menu),
|
||||
completer: Box::new(menu_completer),
|
||||
}
|
||||
} else {
|
||||
ReedlineMenu::EngineCompleter(Box::new(ide_menu))
|
||||
};
|
||||
|
||||
Ok(line_editor.with_menu(completer))
|
||||
}
|
||||
|
||||
// Adds a description menu to the line editor
|
||||
@ -623,34 +599,27 @@ pub(crate) fn add_description_menu(
|
||||
let only_buffer_difference = menu.only_buffer_difference.as_bool()?;
|
||||
description_menu = description_menu.with_only_buffer_difference(only_buffer_difference);
|
||||
|
||||
let span = menu.source.span();
|
||||
match &menu.source {
|
||||
Value::Nothing { .. } => {
|
||||
let completer = Box::new(NuHelpCompleter::new(engine_state, config));
|
||||
Ok(line_editor.with_menu(ReedlineMenu::WithCompleter {
|
||||
menu: Box::new(description_menu),
|
||||
completer,
|
||||
}))
|
||||
let completer = if let Some(closure) = &menu.source {
|
||||
let menu_completer = NuMenuCompleter::new(
|
||||
closure.block_id,
|
||||
span,
|
||||
stack.captures_to_stack(closure.captures.clone()),
|
||||
engine_state,
|
||||
only_buffer_difference,
|
||||
);
|
||||
ReedlineMenu::WithCompleter {
|
||||
menu: Box::new(description_menu),
|
||||
completer: Box::new(menu_completer),
|
||||
}
|
||||
Value::Closure { val, .. } => {
|
||||
let menu_completer = NuMenuCompleter::new(
|
||||
val.block_id,
|
||||
span,
|
||||
stack.captures_to_stack(val.captures.clone()),
|
||||
engine_state,
|
||||
only_buffer_difference,
|
||||
);
|
||||
Ok(line_editor.with_menu(ReedlineMenu::WithCompleter {
|
||||
menu: Box::new(description_menu),
|
||||
completer: Box::new(menu_completer),
|
||||
}))
|
||||
} else {
|
||||
let menu_completer = NuHelpCompleter::new(engine_state, config);
|
||||
ReedlineMenu::WithCompleter {
|
||||
menu: Box::new(description_menu),
|
||||
completer: Box::new(menu_completer),
|
||||
}
|
||||
_ => Err(ShellError::UnsupportedConfigValue {
|
||||
expected: "closure or omitted value".to_string(),
|
||||
value: menu.source.to_abbreviated_string(&config),
|
||||
span: menu.source.span(),
|
||||
}),
|
||||
}
|
||||
};
|
||||
|
||||
Ok(line_editor.with_menu(completer))
|
||||
}
|
||||
|
||||
fn add_menu_keybindings(keybindings: &mut Keybindings) {
|
||||
@ -774,9 +743,9 @@ fn add_keybinding(
|
||||
"emacs" => add_parsed_keybinding(emacs_keybindings, keybinding, config),
|
||||
"vi_insert" => add_parsed_keybinding(insert_keybindings, keybinding, config),
|
||||
"vi_normal" => add_parsed_keybinding(normal_keybindings, keybinding, config),
|
||||
m => Err(ShellError::UnsupportedConfigValue {
|
||||
expected: "emacs, vi_insert or vi_normal".to_string(),
|
||||
value: m.to_string(),
|
||||
str => Err(ShellError::InvalidValue {
|
||||
valid: "'emacs', 'vi_insert', or 'vi_normal'".into(),
|
||||
actual: format!("'{str}'"),
|
||||
span,
|
||||
}),
|
||||
},
|
||||
@ -794,9 +763,9 @@ fn add_keybinding(
|
||||
|
||||
Ok(())
|
||||
}
|
||||
v => Err(ShellError::UnsupportedConfigValue {
|
||||
expected: "string or list of strings".to_string(),
|
||||
value: v.to_abbreviated_string(config),
|
||||
v => Err(ShellError::RuntimeTypeMismatch {
|
||||
expected: Type::custom("string or list<string>"),
|
||||
actual: v.get_type(),
|
||||
span: v.span(),
|
||||
}),
|
||||
}
|
||||
@ -807,14 +776,17 @@ fn add_parsed_keybinding(
|
||||
keybinding: &ParsedKeybinding,
|
||||
config: &Config,
|
||||
) -> Result<(), ShellError> {
|
||||
let modifier_string = keybinding
|
||||
.modifier
|
||||
.to_expanded_string("", config)
|
||||
.to_ascii_lowercase();
|
||||
let Ok(modifier_str) = keybinding.modifier.as_str().map(str::to_ascii_lowercase) else {
|
||||
return Err(ShellError::RuntimeTypeMismatch {
|
||||
expected: Type::String,
|
||||
actual: keybinding.modifier.get_type(),
|
||||
span: keybinding.modifier.span(),
|
||||
});
|
||||
};
|
||||
|
||||
let mut modifier = KeyModifiers::NONE;
|
||||
if modifier_string != "none" {
|
||||
for part in modifier_string.split('_') {
|
||||
if modifier_str != "none" {
|
||||
for part in modifier_str.split('_') {
|
||||
match part {
|
||||
"control" => modifier |= KeyModifiers::CONTROL,
|
||||
"shift" => modifier |= KeyModifiers::SHIFT,
|
||||
@ -822,26 +794,30 @@ fn add_parsed_keybinding(
|
||||
"super" => modifier |= KeyModifiers::SUPER,
|
||||
"hyper" => modifier |= KeyModifiers::HYPER,
|
||||
"meta" => modifier |= KeyModifiers::META,
|
||||
_ => {
|
||||
return Err(ShellError::UnsupportedConfigValue {
|
||||
expected: "CONTROL, SHIFT, ALT, SUPER, HYPER, META or NONE".to_string(),
|
||||
value: keybinding.modifier.to_abbreviated_string(config),
|
||||
str => {
|
||||
return Err(ShellError::InvalidValue {
|
||||
valid: "'control', 'shift', 'alt', 'super', 'hyper', 'meta', or 'none'"
|
||||
.into(),
|
||||
actual: format!("'{str}'"),
|
||||
span: keybinding.modifier.span(),
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let Ok(keycode) = keybinding.keycode.as_str() else {
|
||||
return Err(ShellError::RuntimeTypeMismatch {
|
||||
expected: Type::String,
|
||||
actual: keybinding.keycode.get_type(),
|
||||
span: keybinding.keycode.span(),
|
||||
});
|
||||
};
|
||||
|
||||
let keycode_str = keybinding
|
||||
.keycode
|
||||
.to_expanded_string("", config)
|
||||
.to_ascii_lowercase();
|
||||
|
||||
let keycode = if let Some(rest) = keycode_str.strip_prefix("char_") {
|
||||
let error = |exp: &str, value| ShellError::UnsupportedConfigValue {
|
||||
expected: exp.to_string(),
|
||||
value,
|
||||
let keycode = if let Some(rest) = keycode.strip_prefix("char_") {
|
||||
let error = |valid: &str, actual: &str| ShellError::InvalidValue {
|
||||
valid: valid.into(),
|
||||
actual: actual.into(),
|
||||
span: keybinding.keycode.span(),
|
||||
};
|
||||
|
||||
@ -851,22 +827,17 @@ fn add_parsed_keybinding(
|
||||
(Some('u'), Some(_)) => {
|
||||
// This will never panic as we know there are at least two symbols
|
||||
let Ok(code_point) = u32::from_str_radix(&rest[1..], 16) else {
|
||||
return Err(error("valid hex code in keycode", keycode_str));
|
||||
return Err(error("a valid hex code", keycode));
|
||||
};
|
||||
|
||||
char::from_u32(code_point).ok_or(error("valid Unicode code point", keycode_str))?
|
||||
}
|
||||
_ => {
|
||||
return Err(error(
|
||||
"format 'char_<char>' or 'char_u<hex code>'",
|
||||
keycode_str,
|
||||
))
|
||||
char::from_u32(code_point).ok_or(error("a valid Unicode code point", keycode))?
|
||||
}
|
||||
_ => return Err(error("'char_<char>' or 'char_u<hex code>'", keycode)),
|
||||
};
|
||||
|
||||
KeyCode::Char(char)
|
||||
} else {
|
||||
match keycode_str.as_str() {
|
||||
match keycode {
|
||||
"backspace" => KeyCode::Backspace,
|
||||
"enter" => KeyCode::Enter,
|
||||
"space" => KeyCode::Char(' '),
|
||||
@ -882,29 +853,28 @@ fn add_parsed_keybinding(
|
||||
"backtab" => KeyCode::BackTab,
|
||||
"delete" => KeyCode::Delete,
|
||||
"insert" => KeyCode::Insert,
|
||||
c if c.starts_with('f') => {
|
||||
let fn_num: u8 = c[1..]
|
||||
.parse()
|
||||
.ok()
|
||||
.filter(|num| matches!(num, 1..=20))
|
||||
.ok_or(ShellError::UnsupportedConfigValue {
|
||||
expected: "(f1|f2|...|f20)".to_string(),
|
||||
value: format!("unknown function key: {c}"),
|
||||
span: keybinding.keycode.span(),
|
||||
})?;
|
||||
KeyCode::F(fn_num)
|
||||
}
|
||||
c if c.starts_with('f') => c[1..]
|
||||
.parse()
|
||||
.ok()
|
||||
.filter(|num| (1..=20).contains(num))
|
||||
.map(KeyCode::F)
|
||||
.ok_or(ShellError::InvalidValue {
|
||||
valid: "'f1', 'f2', ..., or 'f20'".into(),
|
||||
actual: format!("'{c}'"),
|
||||
span: keybinding.keycode.span(),
|
||||
})?,
|
||||
"null" => KeyCode::Null,
|
||||
"esc" | "escape" => KeyCode::Esc,
|
||||
_ => {
|
||||
return Err(ShellError::UnsupportedConfigValue {
|
||||
expected: "crossterm KeyCode".to_string(),
|
||||
value: keybinding.keycode.to_abbreviated_string(config),
|
||||
str => {
|
||||
return Err(ShellError::InvalidValue {
|
||||
valid: "a crossterm KeyCode".into(),
|
||||
actual: format!("'{str}'"),
|
||||
span: keybinding.keycode.span(),
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(event) = parse_event(&keybinding.event, config)? {
|
||||
keybindings.add_binding(modifier, keycode, event);
|
||||
} else {
|
||||
@ -926,8 +896,8 @@ impl<'config> EventType<'config> {
|
||||
.map(Self::Send)
|
||||
.or_else(|_| extract_value("edit", record, span).map(Self::Edit))
|
||||
.or_else(|_| extract_value("until", record, span).map(Self::Until))
|
||||
.map_err(|_| ShellError::MissingConfigValue {
|
||||
missing_value: "send, edit or until".to_string(),
|
||||
.map_err(|_| ShellError::MissingRequiredColumn {
|
||||
column: "'send', 'edit', or 'until'",
|
||||
span,
|
||||
})
|
||||
}
|
||||
@ -965,9 +935,9 @@ fn parse_event(value: &Value, config: &Config) -> Result<Option<ReedlineEvent>,
|
||||
.iter()
|
||||
.map(|value| match parse_event(value, config) {
|
||||
Ok(inner) => match inner {
|
||||
None => Err(ShellError::UnsupportedConfigValue {
|
||||
expected: "List containing valid events".to_string(),
|
||||
value: "Nothing value (null)".to_string(),
|
||||
None => Err(ShellError::RuntimeTypeMismatch {
|
||||
expected: Type::custom("record or table"),
|
||||
actual: value.get_type(),
|
||||
span: value.span(),
|
||||
}),
|
||||
Some(event) => Ok(event),
|
||||
@ -978,9 +948,9 @@ fn parse_event(value: &Value, config: &Config) -> Result<Option<ReedlineEvent>,
|
||||
|
||||
Ok(Some(ReedlineEvent::UntilFound(events)))
|
||||
}
|
||||
v => Err(ShellError::UnsupportedConfigValue {
|
||||
expected: "list of events".to_string(),
|
||||
value: v.to_abbreviated_string(config),
|
||||
v => Err(ShellError::RuntimeTypeMismatch {
|
||||
expected: Type::list(Type::Any),
|
||||
actual: v.get_type(),
|
||||
span: v.span(),
|
||||
}),
|
||||
},
|
||||
@ -990,9 +960,9 @@ fn parse_event(value: &Value, config: &Config) -> Result<Option<ReedlineEvent>,
|
||||
.iter()
|
||||
.map(|value| match parse_event(value, config) {
|
||||
Ok(inner) => match inner {
|
||||
None => Err(ShellError::UnsupportedConfigValue {
|
||||
expected: "List containing valid events".to_string(),
|
||||
value: "Nothing value (null)".to_string(),
|
||||
None => Err(ShellError::RuntimeTypeMismatch {
|
||||
expected: Type::custom("record or table"),
|
||||
actual: value.get_type(),
|
||||
span: value.span(),
|
||||
}),
|
||||
Some(event) => Ok(event),
|
||||
@ -1004,9 +974,9 @@ fn parse_event(value: &Value, config: &Config) -> Result<Option<ReedlineEvent>,
|
||||
Ok(Some(ReedlineEvent::Multiple(events)))
|
||||
}
|
||||
Value::Nothing { .. } => Ok(None),
|
||||
v => Err(ShellError::UnsupportedConfigValue {
|
||||
expected: "record or list of records, null to unbind key".to_string(),
|
||||
value: v.to_abbreviated_string(config),
|
||||
v => Err(ShellError::RuntimeTypeMismatch {
|
||||
expected: Type::custom("record, table, or nothing"),
|
||||
actual: v.get_type(),
|
||||
span: v.span(),
|
||||
}),
|
||||
}
|
||||
@ -1055,12 +1025,12 @@ fn event_from_record(
|
||||
let cmd = extract_value("cmd", record, span)?;
|
||||
ReedlineEvent::ExecuteHostCommand(cmd.to_expanded_string("", config))
|
||||
}
|
||||
v => {
|
||||
return Err(ShellError::UnsupportedConfigValue {
|
||||
expected: "Reedline event".to_string(),
|
||||
value: v.to_string(),
|
||||
str => {
|
||||
return Err(ShellError::InvalidValue {
|
||||
valid: "a reedline event".into(),
|
||||
actual: format!("'{str}'"),
|
||||
span,
|
||||
})
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@ -1153,7 +1123,7 @@ fn edit_from_record(
|
||||
}
|
||||
"insertchar" => {
|
||||
let value = extract_value("value", record, span)?;
|
||||
let char = extract_char(value, config)?;
|
||||
let char = extract_char(value)?;
|
||||
EditCommand::InsertChar(char)
|
||||
}
|
||||
"insertstring" => {
|
||||
@ -1190,17 +1160,17 @@ fn edit_from_record(
|
||||
"redo" => EditCommand::Redo,
|
||||
"cutrightuntil" => {
|
||||
let value = extract_value("value", record, span)?;
|
||||
let char = extract_char(value, config)?;
|
||||
let char = extract_char(value)?;
|
||||
EditCommand::CutRightUntil(char)
|
||||
}
|
||||
"cutrightbefore" => {
|
||||
let value = extract_value("value", record, span)?;
|
||||
let char = extract_char(value, config)?;
|
||||
let char = extract_char(value)?;
|
||||
EditCommand::CutRightBefore(char)
|
||||
}
|
||||
"moverightuntil" => {
|
||||
let value = extract_value("value", record, span)?;
|
||||
let char = extract_char(value, config)?;
|
||||
let char = extract_char(value)?;
|
||||
let select = extract_value("select", record, span)
|
||||
.and_then(|value| value.as_bool())
|
||||
.unwrap_or(false);
|
||||
@ -1208,7 +1178,7 @@ fn edit_from_record(
|
||||
}
|
||||
"moverightbefore" => {
|
||||
let value = extract_value("value", record, span)?;
|
||||
let char = extract_char(value, config)?;
|
||||
let char = extract_char(value)?;
|
||||
let select = extract_value("select", record, span)
|
||||
.and_then(|value| value.as_bool())
|
||||
.unwrap_or(false);
|
||||
@ -1216,17 +1186,17 @@ fn edit_from_record(
|
||||
}
|
||||
"cutleftuntil" => {
|
||||
let value = extract_value("value", record, span)?;
|
||||
let char = extract_char(value, config)?;
|
||||
let char = extract_char(value)?;
|
||||
EditCommand::CutLeftUntil(char)
|
||||
}
|
||||
"cutleftbefore" => {
|
||||
let value = extract_value("value", record, span)?;
|
||||
let char = extract_char(value, config)?;
|
||||
let char = extract_char(value)?;
|
||||
EditCommand::CutLeftBefore(char)
|
||||
}
|
||||
"moveleftuntil" => {
|
||||
let value = extract_value("value", record, span)?;
|
||||
let char = extract_char(value, config)?;
|
||||
let char = extract_char(value)?;
|
||||
let select = extract_value("select", record, span)
|
||||
.and_then(|value| value.as_bool())
|
||||
.unwrap_or(false);
|
||||
@ -1234,7 +1204,7 @@ fn edit_from_record(
|
||||
}
|
||||
"moveleftbefore" => {
|
||||
let value = extract_value("value", record, span)?;
|
||||
let char = extract_char(value, config)?;
|
||||
let char = extract_char(value)?;
|
||||
let select = extract_value("select", record, span)
|
||||
.and_then(|value| value.as_bool())
|
||||
.unwrap_or(false);
|
||||
@ -1251,28 +1221,36 @@ fn edit_from_record(
|
||||
#[cfg(feature = "system-clipboard")]
|
||||
"pastesystem" => EditCommand::PasteSystem,
|
||||
"selectall" => EditCommand::SelectAll,
|
||||
e => {
|
||||
return Err(ShellError::UnsupportedConfigValue {
|
||||
expected: "reedline EditCommand".to_string(),
|
||||
value: e.to_string(),
|
||||
str => {
|
||||
return Err(ShellError::InvalidValue {
|
||||
valid: "a reedline EditCommand".into(),
|
||||
actual: format!("'{str}'"),
|
||||
span,
|
||||
})
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Ok(edit)
|
||||
}
|
||||
|
||||
fn extract_char(value: &Value, config: &Config) -> Result<char, ShellError> {
|
||||
let span = value.span();
|
||||
value
|
||||
.to_expanded_string("", config)
|
||||
.chars()
|
||||
.next()
|
||||
.ok_or_else(|| ShellError::MissingConfigValue {
|
||||
missing_value: "char to insert".to_string(),
|
||||
span,
|
||||
fn extract_char(value: &Value) -> Result<char, ShellError> {
|
||||
if let Ok(str) = value.as_str() {
|
||||
let mut chars = str.chars();
|
||||
match (chars.next(), chars.next()) {
|
||||
(Some(c), None) => Ok(c),
|
||||
_ => Err(ShellError::InvalidValue {
|
||||
valid: "a single character".into(),
|
||||
actual: format!("'{str}'"),
|
||||
span: value.span(),
|
||||
}),
|
||||
}
|
||||
} else {
|
||||
Err(ShellError::RuntimeTypeMismatch {
|
||||
expected: Type::String,
|
||||
actual: value.get_type(),
|
||||
span: value.span(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@ -1401,7 +1379,7 @@ mod test {
|
||||
|
||||
let span = Span::test_data();
|
||||
let b = EventType::try_from_record(&event, span);
|
||||
assert!(matches!(b, Err(ShellError::MissingConfigValue { .. })));
|
||||
assert!(matches!(b, Err(ShellError::MissingRequiredColumn { .. })));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -86,11 +86,12 @@ pub fn eval_hook(
|
||||
);
|
||||
if let Some(err) = working_set.parse_errors.first() {
|
||||
report_parse_error(&working_set, err);
|
||||
|
||||
return Err(ShellError::UnsupportedConfigValue {
|
||||
expected: "valid source code".into(),
|
||||
value: "source code with syntax errors".into(),
|
||||
span,
|
||||
return Err(ShellError::GenericError {
|
||||
error: format!("Failed to run {hook_name} hook"),
|
||||
msg: "source code has errors".into(),
|
||||
span: Some(span),
|
||||
help: None,
|
||||
inner: Vec::new(),
|
||||
});
|
||||
}
|
||||
|
||||
@ -161,10 +162,10 @@ pub fn eval_hook(
|
||||
{
|
||||
val
|
||||
} else {
|
||||
return Err(ShellError::UnsupportedConfigValue {
|
||||
expected: "boolean output".to_string(),
|
||||
value: "other PipelineData variant".to_string(),
|
||||
span: other_span,
|
||||
return Err(ShellError::RuntimeTypeMismatch {
|
||||
expected: Type::Bool,
|
||||
actual: pipeline_data.get_type(),
|
||||
span: pipeline_data.span().unwrap_or(other_span),
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -173,9 +174,9 @@ pub fn eval_hook(
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Err(ShellError::UnsupportedConfigValue {
|
||||
expected: "block".to_string(),
|
||||
value: format!("{}", condition.get_type()),
|
||||
return Err(ShellError::RuntimeTypeMismatch {
|
||||
expected: Type::Closure,
|
||||
actual: condition.get_type(),
|
||||
span: other_span,
|
||||
});
|
||||
}
|
||||
@ -218,11 +219,12 @@ pub fn eval_hook(
|
||||
);
|
||||
if let Some(err) = working_set.parse_errors.first() {
|
||||
report_parse_error(&working_set, err);
|
||||
|
||||
return Err(ShellError::UnsupportedConfigValue {
|
||||
expected: "valid source code".into(),
|
||||
value: "source code with syntax errors".into(),
|
||||
span: source_span,
|
||||
return Err(ShellError::GenericError {
|
||||
error: format!("Failed to run {hook_name} hook"),
|
||||
msg: "source code has errors".into(),
|
||||
span: Some(span),
|
||||
help: None,
|
||||
inner: Vec::new(),
|
||||
});
|
||||
}
|
||||
|
||||
@ -257,9 +259,9 @@ pub fn eval_hook(
|
||||
run_hook(engine_state, stack, val, input, arguments, source_span)?;
|
||||
}
|
||||
other => {
|
||||
return Err(ShellError::UnsupportedConfigValue {
|
||||
expected: "block or string".to_string(),
|
||||
value: format!("{}", other.get_type()),
|
||||
return Err(ShellError::RuntimeTypeMismatch {
|
||||
expected: Type::custom("string or closure"),
|
||||
actual: other.get_type(),
|
||||
span: source_span,
|
||||
});
|
||||
}
|
||||
@ -270,9 +272,9 @@ pub fn eval_hook(
|
||||
output = run_hook(engine_state, stack, val, input, arguments, span)?;
|
||||
}
|
||||
other => {
|
||||
return Err(ShellError::UnsupportedConfigValue {
|
||||
expected: "string, block, record, or list of commands".into(),
|
||||
value: format!("{}", other.get_type()),
|
||||
return Err(ShellError::RuntimeTypeMismatch {
|
||||
expected: Type::custom("string, closure, record, or list"),
|
||||
actual: other.get_type(),
|
||||
span: other.span(),
|
||||
});
|
||||
}
|
||||
|
@ -2,9 +2,8 @@ use crate::{color_record_to_nustyle, lookup_ansi_color_style, text_style::Alignm
|
||||
use nu_ansi_term::{Color, Style};
|
||||
use nu_engine::ClosureEvalOnce;
|
||||
use nu_protocol::{
|
||||
cli_error::CliError,
|
||||
engine::{Closure, EngineState, Stack, StateWorkingSet},
|
||||
Span, Value,
|
||||
engine::{Closure, EngineState, Stack},
|
||||
report_shell_error, Span, Value,
|
||||
};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
@ -70,14 +69,8 @@ impl<'a> StyleComputer<'a> {
|
||||
_ => Style::default(),
|
||||
}
|
||||
}
|
||||
// This is basically a copy of nu_cli::report_error(), but that isn't usable due to
|
||||
// dependencies. While crudely spitting out a bunch of errors like this is not ideal,
|
||||
// currently hook closure errors behave roughly the same.
|
||||
Err(e) => {
|
||||
eprintln!(
|
||||
"Error: {:?}",
|
||||
CliError(&e, &StateWorkingSet::new(self.engine_state))
|
||||
);
|
||||
Err(err) => {
|
||||
report_shell_error(self.engine_state, &err);
|
||||
Style::default()
|
||||
}
|
||||
}
|
||||
|
@ -300,14 +300,14 @@ impl UrlComponents {
|
||||
return Ok(true);
|
||||
}
|
||||
match key {
|
||||
"host" => Err(ShellError::UnsupportedConfigValue {
|
||||
expected: "non-empty string".into(),
|
||||
value: "empty string".into(),
|
||||
"host" => Err(ShellError::InvalidValue {
|
||||
valid: "a non-empty string".into(),
|
||||
actual: format!("'{s}'"),
|
||||
span: value_span,
|
||||
}),
|
||||
"scheme" => Err(ShellError::UnsupportedConfigValue {
|
||||
expected: "non-empty string".into(),
|
||||
value: "empty string".into(),
|
||||
"scheme" => Err(ShellError::InvalidValue {
|
||||
valid: "a non-empty string".into(),
|
||||
actual: format!("'{s}'"),
|
||||
span: value_span,
|
||||
}),
|
||||
_ => Ok(false),
|
||||
|
@ -1,83 +0,0 @@
|
||||
use super::prelude::*;
|
||||
use crate as nu_protocol;
|
||||
use crate::engine::Closure;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, IntoValue, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum CompletionAlgorithm {
|
||||
#[default]
|
||||
Prefix,
|
||||
Fuzzy,
|
||||
}
|
||||
|
||||
impl FromStr for CompletionAlgorithm {
|
||||
type Err = &'static str;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s.to_ascii_lowercase().as_str() {
|
||||
"prefix" => Ok(Self::Prefix),
|
||||
"fuzzy" => Ok(Self::Fuzzy),
|
||||
_ => Err("expected either 'prefix' or 'fuzzy'"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, IntoValue, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum CompletionSort {
|
||||
#[default]
|
||||
Smart,
|
||||
Alphabetical,
|
||||
}
|
||||
|
||||
impl FromStr for CompletionSort {
|
||||
type Err = &'static str;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s.to_ascii_lowercase().as_str() {
|
||||
"smart" => Ok(Self::Smart),
|
||||
"alphabetical" => Ok(Self::Alphabetical),
|
||||
_ => Err("expected either 'smart' or 'alphabetical'"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, IntoValue, Serialize, Deserialize)]
|
||||
pub struct ExternalCompleterConfig {
|
||||
pub enable: bool,
|
||||
pub max_results: i64,
|
||||
pub completer: Option<Closure>,
|
||||
}
|
||||
|
||||
impl Default for ExternalCompleterConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
enable: true,
|
||||
max_results: 100,
|
||||
completer: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, IntoValue, Serialize, Deserialize)]
|
||||
pub struct CompleterConfig {
|
||||
pub sort: CompletionSort,
|
||||
pub case_sensitive: bool,
|
||||
pub quick: bool,
|
||||
pub partial: bool,
|
||||
pub algorithm: CompletionAlgorithm,
|
||||
pub external: ExternalCompleterConfig,
|
||||
pub use_ls_colors: bool,
|
||||
}
|
||||
|
||||
impl Default for CompleterConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
sort: CompletionSort::default(),
|
||||
case_sensitive: false,
|
||||
quick: true,
|
||||
partial: true,
|
||||
algorithm: CompletionAlgorithm::default(),
|
||||
external: ExternalCompleterConfig::default(),
|
||||
use_ls_colors: true,
|
||||
}
|
||||
}
|
||||
}
|
151
crates/nu-protocol/src/config/completions.rs
Normal file
151
crates/nu-protocol/src/config/completions.rs
Normal file
@ -0,0 +1,151 @@
|
||||
use super::{config_update_string_enum, prelude::*};
|
||||
use crate as nu_protocol;
|
||||
use crate::engine::Closure;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, IntoValue, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum CompletionAlgorithm {
|
||||
#[default]
|
||||
Prefix,
|
||||
Fuzzy,
|
||||
}
|
||||
|
||||
impl FromStr for CompletionAlgorithm {
|
||||
type Err = &'static str;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s.to_ascii_lowercase().as_str() {
|
||||
"prefix" => Ok(Self::Prefix),
|
||||
"fuzzy" => Ok(Self::Fuzzy),
|
||||
_ => Err("'prefix' or 'fuzzy'"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UpdateFromValue for CompletionAlgorithm {
|
||||
fn update(&mut self, value: &Value, path: &mut ConfigPath, errors: &mut ConfigErrors) {
|
||||
config_update_string_enum(self, value, path, errors)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, IntoValue, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum CompletionSort {
|
||||
#[default]
|
||||
Smart,
|
||||
Alphabetical,
|
||||
}
|
||||
|
||||
impl FromStr for CompletionSort {
|
||||
type Err = &'static str;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s.to_ascii_lowercase().as_str() {
|
||||
"smart" => Ok(Self::Smart),
|
||||
"alphabetical" => Ok(Self::Alphabetical),
|
||||
_ => Err("'smart' or 'alphabetical'"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UpdateFromValue for CompletionSort {
|
||||
fn update(&mut self, value: &Value, path: &mut ConfigPath, errors: &mut ConfigErrors) {
|
||||
config_update_string_enum(self, value, path, errors)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, IntoValue, Serialize, Deserialize)]
|
||||
pub struct ExternalCompleterConfig {
|
||||
pub enable: bool,
|
||||
pub max_results: i64,
|
||||
pub completer: Option<Closure>,
|
||||
}
|
||||
|
||||
impl Default for ExternalCompleterConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
enable: true,
|
||||
max_results: 100,
|
||||
completer: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UpdateFromValue for ExternalCompleterConfig {
|
||||
fn update<'a>(
|
||||
&mut self,
|
||||
value: &'a Value,
|
||||
path: &mut ConfigPath<'a>,
|
||||
errors: &mut ConfigErrors,
|
||||
) {
|
||||
let Value::Record { val: record, .. } = value else {
|
||||
errors.type_mismatch(path, Type::record(), value);
|
||||
return;
|
||||
};
|
||||
|
||||
for (col, val) in record.iter() {
|
||||
let path = &mut path.push(col);
|
||||
match col.as_str() {
|
||||
"completer" => match val {
|
||||
Value::Nothing { .. } => self.completer = None,
|
||||
Value::Closure { val, .. } => self.completer = Some(val.as_ref().clone()),
|
||||
_ => errors.type_mismatch(path, Type::custom("closure or nothing"), val),
|
||||
},
|
||||
"max_results" => self.max_results.update(val, path, errors),
|
||||
"enable" => self.enable.update(val, path, errors),
|
||||
_ => errors.unknown_option(path, val),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, IntoValue, Serialize, Deserialize)]
|
||||
pub struct CompletionConfig {
|
||||
pub sort: CompletionSort,
|
||||
pub case_sensitive: bool,
|
||||
pub quick: bool,
|
||||
pub partial: bool,
|
||||
pub algorithm: CompletionAlgorithm,
|
||||
pub external: ExternalCompleterConfig,
|
||||
pub use_ls_colors: bool,
|
||||
}
|
||||
|
||||
impl Default for CompletionConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
sort: CompletionSort::default(),
|
||||
case_sensitive: false,
|
||||
quick: true,
|
||||
partial: true,
|
||||
algorithm: CompletionAlgorithm::default(),
|
||||
external: ExternalCompleterConfig::default(),
|
||||
use_ls_colors: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UpdateFromValue for CompletionConfig {
|
||||
fn update<'a>(
|
||||
&mut self,
|
||||
value: &'a Value,
|
||||
path: &mut ConfigPath<'a>,
|
||||
errors: &mut ConfigErrors,
|
||||
) {
|
||||
let Value::Record { val: record, .. } = value else {
|
||||
errors.type_mismatch(path, Type::record(), value);
|
||||
return;
|
||||
};
|
||||
|
||||
for (col, val) in record.iter() {
|
||||
let path = &mut path.push(col);
|
||||
match col.as_str() {
|
||||
"sort" => self.sort.update(val, path, errors),
|
||||
"quick" => self.quick.update(val, path, errors),
|
||||
"partial" => self.partial.update(val, path, errors),
|
||||
"algorithm" => self.algorithm.update(val, path, errors),
|
||||
"case_sensitive" => self.case_sensitive.update(val, path, errors),
|
||||
"external" => self.external.update(val, path, errors),
|
||||
"use_ls_colors" => self.use_ls_colors.update(val, path, errors),
|
||||
_ => errors.unknown_option(path, val),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -6,3 +6,34 @@ pub struct DatetimeFormatConfig {
|
||||
pub normal: Option<String>,
|
||||
pub table: Option<String>,
|
||||
}
|
||||
|
||||
impl UpdateFromValue for DatetimeFormatConfig {
|
||||
fn update<'a>(
|
||||
&mut self,
|
||||
value: &'a Value,
|
||||
path: &mut ConfigPath<'a>,
|
||||
errors: &mut ConfigErrors,
|
||||
) {
|
||||
let Value::Record { val: record, .. } = value else {
|
||||
errors.type_mismatch(path, Type::record(), value);
|
||||
return;
|
||||
};
|
||||
|
||||
for (col, val) in record.iter() {
|
||||
let path = &mut path.push(col);
|
||||
match col.as_str() {
|
||||
"normal" => match val {
|
||||
Value::Nothing { .. } => self.normal = None,
|
||||
Value::String { val, .. } => self.normal = Some(val.clone()),
|
||||
_ => errors.type_mismatch(path, Type::custom("string or nothing"), val),
|
||||
},
|
||||
"table" => match val {
|
||||
Value::Nothing { .. } => self.table = None,
|
||||
Value::String { val, .. } => self.table = Some(val.clone()),
|
||||
_ => errors.type_mismatch(path, Type::custom("string or nothing"), val),
|
||||
},
|
||||
_ => errors.unknown_option(path, val),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -27,3 +27,26 @@ impl Default for DisplayErrors {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UpdateFromValue for DisplayErrors {
|
||||
fn update<'a>(
|
||||
&mut self,
|
||||
value: &'a Value,
|
||||
path: &mut ConfigPath<'a>,
|
||||
errors: &mut ConfigErrors,
|
||||
) {
|
||||
let Value::Record { val: record, .. } = value else {
|
||||
errors.type_mismatch(path, Type::record(), value);
|
||||
return;
|
||||
};
|
||||
|
||||
for (col, val) in record.iter() {
|
||||
let path = &mut path.push(col);
|
||||
match col.as_str() {
|
||||
"exit_code" => self.exit_code.update(val, path, errors),
|
||||
"termination_signal" => self.termination_signal.update(val, path, errors),
|
||||
_ => errors.unknown_option(path, val),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
85
crates/nu-protocol/src/config/error.rs
Normal file
85
crates/nu-protocol/src/config/error.rs
Normal file
@ -0,0 +1,85 @@
|
||||
use super::ConfigPath;
|
||||
use crate::{Config, ConfigError, ShellError, Span, Type, Value};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(super) struct ConfigErrors<'a> {
|
||||
config: &'a Config,
|
||||
errors: Vec<ConfigError>,
|
||||
}
|
||||
|
||||
impl<'a> ConfigErrors<'a> {
|
||||
pub fn new(config: &'a Config) -> Self {
|
||||
Self {
|
||||
config,
|
||||
errors: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.errors.is_empty()
|
||||
}
|
||||
|
||||
pub fn error(&mut self, error: ConfigError) {
|
||||
self.errors.push(error);
|
||||
}
|
||||
|
||||
pub fn type_mismatch(&mut self, path: &ConfigPath, expected: Type, actual: &Value) {
|
||||
self.error(ConfigError::TypeMismatch {
|
||||
path: path.to_string(),
|
||||
expected,
|
||||
actual: actual.get_type(),
|
||||
span: actual.span(),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn invalid_value(
|
||||
&mut self,
|
||||
path: &ConfigPath,
|
||||
expected: impl Into<String>,
|
||||
actual: &Value,
|
||||
) {
|
||||
self.error(ConfigError::InvalidValue {
|
||||
path: path.to_string(),
|
||||
valid: expected.into(),
|
||||
actual: if let Ok(str) = actual.as_str() {
|
||||
format!("'{str}'")
|
||||
} else {
|
||||
actual.to_abbreviated_string(self.config)
|
||||
},
|
||||
span: actual.span(),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn missing_column(&mut self, path: &ConfigPath, column: &'static str, span: Span) {
|
||||
self.error(ConfigError::MissingRequiredColumn {
|
||||
path: path.to_string(),
|
||||
column,
|
||||
span,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn unknown_option(&mut self, path: &ConfigPath, value: &Value) {
|
||||
self.error(ConfigError::UnknownOption {
|
||||
path: path.to_string(),
|
||||
span: value.span(),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn deprecated_option(&mut self, path: &ConfigPath, suggestion: &'static str, span: Span) {
|
||||
self.error(ConfigError::Deprecated {
|
||||
path: path.to_string(),
|
||||
suggestion,
|
||||
span,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn into_shell_error(self) -> Option<ShellError> {
|
||||
if self.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(ShellError::InvalidConfig {
|
||||
errors: self.errors,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
@ -15,3 +15,26 @@ impl Default for FilesizeConfig {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UpdateFromValue for FilesizeConfig {
|
||||
fn update<'a>(
|
||||
&mut self,
|
||||
value: &'a Value,
|
||||
path: &mut ConfigPath<'a>,
|
||||
errors: &mut ConfigErrors,
|
||||
) {
|
||||
let Value::Record { val: record, .. } = value else {
|
||||
errors.type_mismatch(path, Type::record(), value);
|
||||
return;
|
||||
};
|
||||
|
||||
for (col, val) in record.iter() {
|
||||
let path = &mut path.push(col);
|
||||
match col.as_str() {
|
||||
"metric" => self.metric.update(val, path, errors),
|
||||
"format" => self.format.update(val, path, errors),
|
||||
_ => errors.unknown_option(path, val),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,132 +1,171 @@
|
||||
use crate::{IntoValue, Record, ShellError, Span, Value};
|
||||
use std::{collections::HashMap, fmt::Display, str::FromStr};
|
||||
use super::error::ConfigErrors;
|
||||
use crate::{Record, ShellError, Span, Type, Value};
|
||||
use std::{
|
||||
borrow::Borrow,
|
||||
collections::HashMap,
|
||||
fmt::{self, Display},
|
||||
hash::Hash,
|
||||
ops::{Deref, DerefMut},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
pub(super) fn process_string_enum<T, E>(
|
||||
config_point: &mut T,
|
||||
config_path: &[&str],
|
||||
value: &mut Value,
|
||||
errors: &mut Vec<ShellError>,
|
||||
) where
|
||||
T: FromStr<Err = E> + Clone + IntoValue,
|
||||
E: Display,
|
||||
pub(super) struct ConfigPath<'a> {
|
||||
components: Vec<&'a str>,
|
||||
}
|
||||
|
||||
impl<'a> ConfigPath<'a> {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
components: vec!["$env.config"],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push(&mut self, key: &'a str) -> ConfigPathScope<'_, 'a> {
|
||||
self.components.push(key);
|
||||
ConfigPathScope { inner: self }
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ConfigPath<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.components.join("."))
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) struct ConfigPathScope<'whole, 'part> {
|
||||
inner: &'whole mut ConfigPath<'part>,
|
||||
}
|
||||
|
||||
impl Drop for ConfigPathScope<'_, '_> {
|
||||
fn drop(&mut self) {
|
||||
self.inner.components.pop();
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Deref for ConfigPathScope<'_, 'a> {
|
||||
type Target = ConfigPath<'a>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for ConfigPathScope<'_, '_> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
self.inner
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) trait UpdateFromValue: Sized {
|
||||
fn update<'a>(
|
||||
&mut self,
|
||||
value: &'a Value,
|
||||
path: &mut ConfigPath<'a>,
|
||||
errors: &mut ConfigErrors,
|
||||
);
|
||||
}
|
||||
|
||||
impl UpdateFromValue for Value {
|
||||
fn update(&mut self, value: &Value, _path: &mut ConfigPath, _errors: &mut ConfigErrors) {
|
||||
*self = value.clone();
|
||||
}
|
||||
}
|
||||
|
||||
impl UpdateFromValue for bool {
|
||||
fn update(&mut self, value: &Value, path: &mut ConfigPath, errors: &mut ConfigErrors) {
|
||||
if let Ok(val) = value.as_bool() {
|
||||
*self = val;
|
||||
} else {
|
||||
errors.type_mismatch(path, Type::Bool, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UpdateFromValue for i64 {
|
||||
fn update(&mut self, value: &Value, path: &mut ConfigPath, errors: &mut ConfigErrors) {
|
||||
if let Ok(val) = value.as_int() {
|
||||
*self = val;
|
||||
} else {
|
||||
errors.type_mismatch(path, Type::Int, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UpdateFromValue for usize {
|
||||
fn update(&mut self, value: &Value, path: &mut ConfigPath, errors: &mut ConfigErrors) {
|
||||
if let Ok(val) = value.as_int() {
|
||||
if let Ok(val) = val.try_into() {
|
||||
*self = val;
|
||||
} else {
|
||||
errors.invalid_value(path, "a non-negative integer", value);
|
||||
}
|
||||
} else {
|
||||
errors.type_mismatch(path, Type::Int, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UpdateFromValue for String {
|
||||
fn update(&mut self, value: &Value, path: &mut ConfigPath, errors: &mut ConfigErrors) {
|
||||
if let Ok(val) = value.as_str() {
|
||||
*self = val.into();
|
||||
} else {
|
||||
errors.type_mismatch(path, Type::String, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V> UpdateFromValue for HashMap<K, V>
|
||||
where
|
||||
K: Borrow<str> + for<'a> From<&'a str> + Eq + Hash,
|
||||
V: Default + UpdateFromValue,
|
||||
{
|
||||
let span = value.span();
|
||||
if let Ok(v) = value.coerce_str() {
|
||||
match v.parse() {
|
||||
Ok(format) => {
|
||||
*config_point = format;
|
||||
}
|
||||
Err(err) => {
|
||||
errors.push(ShellError::GenericError {
|
||||
error: "Error while applying config changes".into(),
|
||||
msg: format!(
|
||||
"unrecognized $env.config.{} option '{v}'",
|
||||
config_path.join(".")
|
||||
),
|
||||
span: Some(span),
|
||||
help: Some(err.to_string()),
|
||||
inner: vec![],
|
||||
});
|
||||
// Reconstruct
|
||||
*value = config_point.clone().into_value(span);
|
||||
}
|
||||
fn update<'a>(
|
||||
&mut self,
|
||||
value: &'a Value,
|
||||
path: &mut ConfigPath<'a>,
|
||||
errors: &mut ConfigErrors,
|
||||
) {
|
||||
if let Ok(record) = value.as_record() {
|
||||
*self = record
|
||||
.iter()
|
||||
.map(|(key, val)| {
|
||||
let mut old = self.remove(key).unwrap_or_default();
|
||||
old.update(val, &mut path.push(key), errors);
|
||||
(key.as_str().into(), old)
|
||||
})
|
||||
.collect();
|
||||
} else {
|
||||
errors.type_mismatch(path, Type::record(), value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn config_update_string_enum<T>(
|
||||
choice: &mut T,
|
||||
value: &Value,
|
||||
path: &mut ConfigPath,
|
||||
errors: &mut ConfigErrors,
|
||||
) where
|
||||
T: FromStr,
|
||||
T::Err: Display,
|
||||
{
|
||||
if let Ok(str) = value.as_str() {
|
||||
match str.parse() {
|
||||
Ok(val) => *choice = val,
|
||||
Err(err) => errors.invalid_value(path, err.to_string(), value),
|
||||
}
|
||||
} else {
|
||||
errors.push(ShellError::GenericError {
|
||||
error: "Error while applying config changes".into(),
|
||||
msg: format!("$env.config.{} should be a string", config_path.join(".")),
|
||||
span: Some(span),
|
||||
help: Some("This value will be ignored.".into()),
|
||||
inner: vec![],
|
||||
});
|
||||
// Reconstruct
|
||||
*value = config_point.clone().into_value(span);
|
||||
errors.type_mismatch(path, Type::String, value);
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn process_bool_config(
|
||||
value: &mut Value,
|
||||
errors: &mut Vec<ShellError>,
|
||||
config_point: &mut bool,
|
||||
) {
|
||||
if let Ok(b) = value.as_bool() {
|
||||
*config_point = b;
|
||||
} else {
|
||||
errors.push(ShellError::GenericError {
|
||||
error: "Error while applying config changes".into(),
|
||||
msg: "should be a bool".to_string(),
|
||||
span: Some(value.span()),
|
||||
help: Some("This value will be ignored.".into()),
|
||||
inner: vec![],
|
||||
});
|
||||
// Reconstruct
|
||||
*value = Value::bool(*config_point, value.span());
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn process_int_config(
|
||||
value: &mut Value,
|
||||
errors: &mut Vec<ShellError>,
|
||||
config_point: &mut i64,
|
||||
) {
|
||||
if let Ok(b) = value.as_int() {
|
||||
*config_point = b;
|
||||
} else {
|
||||
errors.push(ShellError::GenericError {
|
||||
error: "Error while applying config changes".into(),
|
||||
msg: "should be an int".into(),
|
||||
span: Some(value.span()),
|
||||
help: Some("This value will be ignored.".into()),
|
||||
inner: vec![],
|
||||
});
|
||||
// Reconstruct
|
||||
*value = Value::int(*config_point, value.span());
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn report_invalid_key(keys: &[&str], span: Span, errors: &mut Vec<ShellError>) {
|
||||
// Because Value::Record discards all of the spans of its
|
||||
// column names (by storing them as Strings), the key name cannot be provided
|
||||
// as a value, even in key errors.
|
||||
errors.push(ShellError::GenericError {
|
||||
error: "Error while applying config changes".into(),
|
||||
msg: format!(
|
||||
"$env.config.{} is an unknown config setting",
|
||||
keys.join(".")
|
||||
),
|
||||
span: Some(span),
|
||||
help: Some("This value will not appear in your $env.config record.".into()),
|
||||
inner: vec![],
|
||||
});
|
||||
}
|
||||
|
||||
pub(super) fn report_invalid_value(msg: &str, span: Span, errors: &mut Vec<ShellError>) {
|
||||
errors.push(ShellError::GenericError {
|
||||
error: "Error while applying config changes".into(),
|
||||
msg: msg.into(),
|
||||
span: Some(span),
|
||||
help: Some("This value will be ignored.".into()),
|
||||
inner: vec![],
|
||||
});
|
||||
}
|
||||
|
||||
pub(super) fn create_map(value: &Value) -> Result<HashMap<String, Value>, ShellError> {
|
||||
Ok(value
|
||||
.as_record()?
|
||||
.iter()
|
||||
.map(|(k, v)| (k.clone(), v.clone()))
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub fn extract_value<'record>(
|
||||
name: &str,
|
||||
column: &'static str,
|
||||
record: &'record Record,
|
||||
span: Span,
|
||||
) -> Result<&'record Value, ShellError> {
|
||||
record
|
||||
.get(name)
|
||||
.ok_or_else(|| ShellError::MissingConfigValue {
|
||||
missing_value: name.to_string(),
|
||||
span,
|
||||
})
|
||||
.get(column)
|
||||
.ok_or_else(|| ShellError::MissingRequiredColumn { column, span })
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
use super::prelude::*;
|
||||
use super::{config_update_string_enum, prelude::*};
|
||||
use crate as nu_protocol;
|
||||
|
||||
#[derive(Clone, Copy, Debug, IntoValue, PartialEq, Eq, Serialize, Deserialize)]
|
||||
@ -26,11 +26,17 @@ impl FromStr for HistoryFileFormat {
|
||||
match s.to_ascii_lowercase().as_str() {
|
||||
"sqlite" => Ok(Self::Sqlite),
|
||||
"plaintext" => Ok(Self::Plaintext),
|
||||
_ => Err("expected either 'sqlite' or 'plaintext'"),
|
||||
_ => Err("'sqlite' or 'plaintext'"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UpdateFromValue for HistoryFileFormat {
|
||||
fn update(&mut self, value: &Value, path: &mut ConfigPath, errors: &mut ConfigErrors) {
|
||||
config_update_string_enum(self, value, path, errors)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, IntoValue, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct HistoryConfig {
|
||||
pub max_size: i64,
|
||||
@ -58,3 +64,28 @@ impl Default for HistoryConfig {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UpdateFromValue for HistoryConfig {
|
||||
fn update<'a>(
|
||||
&mut self,
|
||||
value: &'a Value,
|
||||
path: &mut ConfigPath<'a>,
|
||||
errors: &mut ConfigErrors,
|
||||
) {
|
||||
let Value::Record { val: record, .. } = value else {
|
||||
errors.type_mismatch(path, Type::record(), value);
|
||||
return;
|
||||
};
|
||||
|
||||
for (col, val) in record.iter() {
|
||||
let path = &mut path.push(col);
|
||||
match col.as_str() {
|
||||
"isolation" => self.isolation.update(val, path, errors),
|
||||
"sync_on_enter" => self.sync_on_enter.update(val, path, errors),
|
||||
"max_size" => self.max_size.update(val, path, errors),
|
||||
"file_format" => self.file_format.update(val, path, errors),
|
||||
_ => errors.unknown_option(path, val),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
use super::prelude::*;
|
||||
use crate as nu_protocol;
|
||||
use crate::ShellError;
|
||||
|
||||
/// Definition of a parsed hook from the config object
|
||||
#[derive(Clone, Debug, IntoValue, PartialEq, Serialize, Deserialize)]
|
||||
@ -33,36 +32,36 @@ impl Default for Hooks {
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse the hooks to find the blocks to run when the hooks fire
|
||||
pub(super) fn create_hooks(value: &Value) -> Result<Hooks, ShellError> {
|
||||
let span = value.span();
|
||||
match value {
|
||||
Value::Record { val, .. } => {
|
||||
let mut hooks = Hooks::new();
|
||||
|
||||
for (col, val) in &**val {
|
||||
match col.as_str() {
|
||||
"pre_prompt" => hooks.pre_prompt = Some(val.clone()),
|
||||
"pre_execution" => hooks.pre_execution = Some(val.clone()),
|
||||
"env_change" => hooks.env_change = Some(val.clone()),
|
||||
"display_output" => hooks.display_output = Some(val.clone()),
|
||||
"command_not_found" => hooks.command_not_found = Some(val.clone()),
|
||||
x => {
|
||||
return Err(ShellError::UnsupportedConfigValue {
|
||||
expected: "'pre_prompt', 'pre_execution', 'env_change', 'display_output', 'command_not_found'".into(),
|
||||
value: x.into(),
|
||||
span
|
||||
});
|
||||
}
|
||||
}
|
||||
impl UpdateFromValue for Hooks {
|
||||
fn update<'a>(
|
||||
&mut self,
|
||||
value: &'a Value,
|
||||
path: &mut ConfigPath<'a>,
|
||||
errors: &mut ConfigErrors,
|
||||
) {
|
||||
fn update_option(field: &mut Option<Value>, value: &Value) {
|
||||
if value.is_nothing() {
|
||||
*field = None;
|
||||
} else {
|
||||
*field = Some(value.clone());
|
||||
}
|
||||
}
|
||||
|
||||
let Value::Record { val: record, .. } = value else {
|
||||
errors.type_mismatch(path, Type::record(), value);
|
||||
return;
|
||||
};
|
||||
|
||||
for (col, val) in record.iter() {
|
||||
let path = &mut path.push(col);
|
||||
match col.as_str() {
|
||||
"pre_prompt" => update_option(&mut self.pre_prompt, val),
|
||||
"pre_execution" => update_option(&mut self.pre_execution, val),
|
||||
"env_change" => update_option(&mut self.env_change, val),
|
||||
"display_output" => update_option(&mut self.display_output, val),
|
||||
"command_not_found" => update_option(&mut self.command_not_found, val),
|
||||
_ => errors.unknown_option(path, val),
|
||||
}
|
||||
|
||||
Ok(hooks)
|
||||
}
|
||||
_ => Err(ShellError::UnsupportedConfigValue {
|
||||
expected: "record for 'hooks' config".into(),
|
||||
value: "non-record value".into(),
|
||||
span,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
@ -15,3 +15,26 @@ impl Default for LsConfig {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UpdateFromValue for LsConfig {
|
||||
fn update<'a>(
|
||||
&mut self,
|
||||
value: &'a Value,
|
||||
path: &mut ConfigPath<'a>,
|
||||
errors: &mut ConfigErrors,
|
||||
) {
|
||||
let Value::Record { val: record, .. } = value else {
|
||||
errors.type_mismatch(path, Type::record(), value);
|
||||
return;
|
||||
};
|
||||
|
||||
for (col, val) in record.iter() {
|
||||
let path = &mut path.push(col);
|
||||
match col.as_str() {
|
||||
"use_ls_colors" => self.use_ls_colors.update(val, path, errors),
|
||||
"clickable_links" => self.clickable_links.update(val, path, errors),
|
||||
_ => errors.unknown_option(path, val),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,35 +1,32 @@
|
||||
//! Module containing the internal representation of user configuration
|
||||
use self::helper::*;
|
||||
use self::hooks::*;
|
||||
|
||||
use crate::{IntoValue, ShellError, Span, Value};
|
||||
use reedline::create_keybindings;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use crate as nu_protocol;
|
||||
use crate::FromValue;
|
||||
use helper::*;
|
||||
use prelude::*;
|
||||
use std::collections::HashMap;
|
||||
use table::try_parse_trim_strategy;
|
||||
|
||||
pub use self::completer::{
|
||||
CompleterConfig, CompletionAlgorithm, CompletionSort, ExternalCompleterConfig,
|
||||
pub use completions::{
|
||||
CompletionAlgorithm, CompletionConfig, CompletionSort, ExternalCompleterConfig,
|
||||
};
|
||||
pub use self::datetime_format::DatetimeFormatConfig;
|
||||
pub use self::display_errors::DisplayErrors;
|
||||
pub use self::filesize::FilesizeConfig;
|
||||
pub use self::helper::extract_value;
|
||||
pub use self::history::{HistoryConfig, HistoryFileFormat};
|
||||
pub use self::hooks::Hooks;
|
||||
pub use self::ls::LsConfig;
|
||||
pub use self::output::ErrorStyle;
|
||||
pub use self::plugin_gc::{PluginGcConfig, PluginGcConfigs};
|
||||
pub use self::reedline::{
|
||||
create_menus, CursorShapeConfig, EditBindings, NuCursorShape, ParsedKeybinding, ParsedMenu,
|
||||
};
|
||||
pub use self::rm::RmConfig;
|
||||
pub use self::shell_integration::ShellIntegrationConfig;
|
||||
pub use self::table::{FooterMode, TableConfig, TableIndexMode, TableMode, TrimStrategy};
|
||||
pub use datetime_format::DatetimeFormatConfig;
|
||||
pub use display_errors::DisplayErrors;
|
||||
pub use filesize::FilesizeConfig;
|
||||
pub use helper::extract_value;
|
||||
pub use history::{HistoryConfig, HistoryFileFormat};
|
||||
pub use hooks::Hooks;
|
||||
pub use ls::LsConfig;
|
||||
pub use output::ErrorStyle;
|
||||
pub use plugin_gc::{PluginGcConfig, PluginGcConfigs};
|
||||
pub use reedline::{CursorShapeConfig, EditBindings, NuCursorShape, ParsedKeybinding, ParsedMenu};
|
||||
pub use rm::RmConfig;
|
||||
pub use shell_integration::ShellIntegrationConfig;
|
||||
pub use table::{FooterMode, TableConfig, TableIndexMode, TableMode, TrimStrategy};
|
||||
|
||||
mod completer;
|
||||
mod completions;
|
||||
mod datetime_format;
|
||||
mod display_errors;
|
||||
mod error;
|
||||
mod filesize;
|
||||
mod helper;
|
||||
mod history;
|
||||
@ -43,7 +40,7 @@ mod rm;
|
||||
mod shell_integration;
|
||||
mod table;
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
#[derive(Clone, Debug, IntoValue, Serialize, Deserialize)]
|
||||
pub struct Config {
|
||||
pub filesize: FilesizeConfig,
|
||||
pub table: TableConfig,
|
||||
@ -53,7 +50,7 @@ pub struct Config {
|
||||
pub float_precision: i64,
|
||||
pub recursion_limit: i64,
|
||||
pub use_ansi_coloring: bool,
|
||||
pub completions: CompleterConfig,
|
||||
pub completions: CompletionConfig,
|
||||
pub edit_mode: EditBindings,
|
||||
pub history: HistoryConfig,
|
||||
pub keybindings: Vec<ParsedKeybinding>,
|
||||
@ -97,7 +94,7 @@ impl Default for Config {
|
||||
|
||||
history: HistoryConfig::default(),
|
||||
|
||||
completions: CompleterConfig::default(),
|
||||
completions: CompletionConfig::default(),
|
||||
|
||||
recursion_limit: 50,
|
||||
|
||||
@ -135,630 +132,104 @@ impl Default for Config {
|
||||
}
|
||||
}
|
||||
|
||||
impl Value {
|
||||
/// Parse the given [`Value`] as a configuration record, and recover encountered mistakes
|
||||
///
|
||||
/// If any given (sub)value is detected as impossible, this value will be restored to the value
|
||||
/// in `existing_config`, thus mutates `self`.
|
||||
///
|
||||
/// Returns a new [`Config`] (that is in a valid state) and if encountered the [`ShellError`]
|
||||
/// containing all observed inner errors.
|
||||
pub fn parse_as_config(&mut self, existing_config: &Config) -> (Config, Option<ShellError>) {
|
||||
// Clone the passed-in config rather than mutating it.
|
||||
let mut config = existing_config.clone();
|
||||
|
||||
// Vec for storing errors. Current Nushell behaviour (Dec 2022) is that having some typo
|
||||
// like `"always_trash": tru` in your config.nu's `$env.config` record shouldn't abort all
|
||||
// config parsing there and then. Thus, errors are simply collected one-by-one and wrapped
|
||||
// in a GenericError at the end.
|
||||
let mut errors = vec![];
|
||||
|
||||
// Config record (self) mutation rules:
|
||||
// * When parsing a config Record, if a config key error occurs, remove the key.
|
||||
// * When parsing a config Record, if a config value error occurs, replace the value
|
||||
// with a reconstructed Nu value for the current (unaltered) configuration for that setting.
|
||||
// For instance:
|
||||
// `$env.config.ls.use_ls_colors = 2` results in an error, so the current `use_ls_colors`
|
||||
// config setting is converted to a `Value::Boolean` and inserted in the record in place of
|
||||
// the `2`.
|
||||
|
||||
let Value::Record { val, .. } = self else {
|
||||
return (
|
||||
config,
|
||||
Some(ShellError::GenericError {
|
||||
error: "Error while applying config changes".into(),
|
||||
msg: "$env.config is not a record".into(),
|
||||
span: Some(self.span()),
|
||||
help: None,
|
||||
inner: vec![],
|
||||
}),
|
||||
);
|
||||
impl UpdateFromValue for Config {
|
||||
fn update<'a>(
|
||||
&mut self,
|
||||
value: &'a Value,
|
||||
path: &mut ConfigPath<'a>,
|
||||
errors: &mut ConfigErrors,
|
||||
) {
|
||||
let Value::Record { val: record, .. } = value else {
|
||||
errors.type_mismatch(path, Type::record(), value);
|
||||
return;
|
||||
};
|
||||
|
||||
val.to_mut().retain_mut(|key, value| {
|
||||
let span = value.span();
|
||||
match key {
|
||||
"ls" => {
|
||||
if let Value::Record { val, .. } = value {
|
||||
val.to_mut().retain_mut(|key2, value| {
|
||||
let span = value.span();
|
||||
match key2 {
|
||||
"use_ls_colors" => {
|
||||
process_bool_config(value, &mut errors, &mut config.ls.use_ls_colors);
|
||||
}
|
||||
"clickable_links" => {
|
||||
process_bool_config(value, &mut errors, &mut config.ls.clickable_links);
|
||||
}
|
||||
_ => {
|
||||
report_invalid_key(&[key, key2], span, &mut errors);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
});
|
||||
} else {
|
||||
report_invalid_value("should be a record", span, &mut errors);
|
||||
*value = config.ls.into_value(span);
|
||||
}
|
||||
}
|
||||
"rm" => {
|
||||
if let Value::Record { val, .. } = value {
|
||||
val.to_mut().retain_mut(|key2, value| {
|
||||
let span = value.span();
|
||||
match key2 {
|
||||
"always_trash" => {
|
||||
process_bool_config(value, &mut errors, &mut config.rm.always_trash);
|
||||
}
|
||||
_ => {
|
||||
report_invalid_key(&[key, key2], span, &mut errors);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
true
|
||||
});
|
||||
} else {
|
||||
report_invalid_value("should be a record", span, &mut errors);
|
||||
*value = config.rm.into_value(span);
|
||||
}
|
||||
}
|
||||
"history" => {
|
||||
let history = &mut config.history;
|
||||
if let Value::Record { val, .. } = value {
|
||||
val.to_mut().retain_mut(|key2, value| {
|
||||
let span = value.span();
|
||||
match key2 {
|
||||
"isolation" => {
|
||||
process_bool_config(value, &mut errors, &mut history.isolation);
|
||||
}
|
||||
"sync_on_enter" => {
|
||||
process_bool_config(value, &mut errors, &mut history.sync_on_enter);
|
||||
}
|
||||
"max_size" => {
|
||||
process_int_config(value, &mut errors, &mut history.max_size);
|
||||
}
|
||||
"file_format" => {
|
||||
process_string_enum(
|
||||
&mut history.file_format,
|
||||
&[key, key2],
|
||||
value,
|
||||
&mut errors
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
report_invalid_key(&[key, key2], span, &mut errors);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
true
|
||||
});
|
||||
} else {
|
||||
report_invalid_value("should be a record", span, &mut errors);
|
||||
*value = config.history.into_value(span);
|
||||
}
|
||||
}
|
||||
"completions" => {
|
||||
if let Value::Record { val, .. } = value {
|
||||
val.to_mut().retain_mut(|key2, value| {
|
||||
let span = value.span();
|
||||
match key2 {
|
||||
"quick" => {
|
||||
process_bool_config(value, &mut errors, &mut config.completions.quick);
|
||||
}
|
||||
"partial" => {
|
||||
process_bool_config(value, &mut errors, &mut config.completions.partial);
|
||||
}
|
||||
"algorithm" => {
|
||||
process_string_enum(
|
||||
&mut config.completions.algorithm,
|
||||
&[key, key2],
|
||||
value,
|
||||
&mut errors
|
||||
);
|
||||
}
|
||||
"case_sensitive" => {
|
||||
process_bool_config(value, &mut errors, &mut config.completions.case_sensitive);
|
||||
}
|
||||
"sort" => {
|
||||
process_string_enum(
|
||||
&mut config.completions.sort,
|
||||
&[key, key2],
|
||||
value,
|
||||
&mut errors
|
||||
);
|
||||
}
|
||||
"external" => {
|
||||
if let Value::Record { val, .. } = value {
|
||||
val.to_mut().retain_mut(|key3, value| {
|
||||
let span = value.span();
|
||||
match key3 {
|
||||
"max_results" => {
|
||||
process_int_config(value, &mut errors, &mut config.completions.external.max_results);
|
||||
}
|
||||
"completer" => {
|
||||
if let Ok(v) = value.as_closure() {
|
||||
config.completions.external.completer = Some(v.clone())
|
||||
} else {
|
||||
match value {
|
||||
Value::Nothing { .. } => {}
|
||||
_ => {
|
||||
report_invalid_value("should be a closure or null", span, &mut errors);
|
||||
*value = config.completions.external.completer.clone().into_value(span);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"enable" => {
|
||||
process_bool_config(value, &mut errors, &mut config.completions.external.enable);
|
||||
}
|
||||
_ => {
|
||||
report_invalid_key(&[key, key2, key3], span, &mut errors);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
true
|
||||
});
|
||||
} else {
|
||||
report_invalid_value("should be a record", span, &mut errors);
|
||||
*value = config.completions.external.clone().into_value(span);
|
||||
}
|
||||
}
|
||||
"use_ls_colors" => {
|
||||
process_bool_config(value, &mut errors, &mut config.completions.use_ls_colors);
|
||||
}
|
||||
_ => {
|
||||
report_invalid_key(&[key, key2], span, &mut errors);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
true
|
||||
});
|
||||
} else {
|
||||
report_invalid_value("should be a record", span, &mut errors);
|
||||
*value = config.completions.clone().into_value(span);
|
||||
}
|
||||
}
|
||||
"cursor_shape" => {
|
||||
if let Value::Record { val, .. } = value {
|
||||
val.to_mut().retain_mut(|key2, value| {
|
||||
let span = value.span();
|
||||
let config_point = match key2 {
|
||||
"vi_insert" => &mut config.cursor_shape.vi_insert,
|
||||
"vi_normal" => &mut config.cursor_shape.vi_normal,
|
||||
"emacs" => &mut config.cursor_shape.emacs,
|
||||
_ => {
|
||||
report_invalid_key(&[key, key2], span, &mut errors);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
process_string_enum(
|
||||
config_point,
|
||||
&[key, key2],
|
||||
value,
|
||||
&mut errors
|
||||
);
|
||||
true
|
||||
});
|
||||
} else {
|
||||
report_invalid_value("should be a record", span, &mut errors);
|
||||
*value = config.cursor_shape.into_value(span);
|
||||
}
|
||||
}
|
||||
"table" => {
|
||||
if let Value::Record { val, .. } = value {
|
||||
val.to_mut().retain_mut(|key2, value| {
|
||||
let span = value.span();
|
||||
match key2 {
|
||||
"mode" => {
|
||||
process_string_enum(
|
||||
&mut config.table.mode,
|
||||
&[key, key2],
|
||||
value,
|
||||
&mut errors
|
||||
);
|
||||
}
|
||||
"header_on_separator" => {
|
||||
process_bool_config(value, &mut errors, &mut config.table.header_on_separator);
|
||||
}
|
||||
"padding" => match value {
|
||||
Value::Int { val, .. } => {
|
||||
if *val < 0 {
|
||||
report_invalid_value("expected a unsigned integer", span, &mut errors);
|
||||
*value = config.table.padding.into_value(span);
|
||||
} else {
|
||||
config.table.padding.left = *val as usize;
|
||||
config.table.padding.right = *val as usize;
|
||||
}
|
||||
}
|
||||
Value::Record { val, .. } => {
|
||||
let mut invalid = false;
|
||||
val.to_mut().retain(|key3, value| {
|
||||
match key3 {
|
||||
"left" => {
|
||||
match value.as_int() {
|
||||
Ok(val) if val >= 0 => {
|
||||
config.table.padding.left = val as usize;
|
||||
}
|
||||
_ => {
|
||||
report_invalid_value("expected a unsigned integer >= 0", span, &mut errors);
|
||||
invalid = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
"right" => {
|
||||
match value.as_int() {
|
||||
Ok(val) if val >= 0 => {
|
||||
config.table.padding.right = val as usize;
|
||||
}
|
||||
_ => {
|
||||
report_invalid_value("expected a unsigned integer >= 0", span, &mut errors);
|
||||
invalid = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
report_invalid_key(&[key, key2, key3], span, &mut errors);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
true
|
||||
});
|
||||
if invalid {
|
||||
*value = config.table.padding.into_value(span);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
report_invalid_value("expected a unsigned integer or a record", span, &mut errors);
|
||||
*value = config.table.padding.into_value(span);
|
||||
}
|
||||
},
|
||||
"index_mode" => {
|
||||
process_string_enum(
|
||||
&mut config.table.index_mode,
|
||||
&[key, key2],
|
||||
value,
|
||||
&mut errors
|
||||
);
|
||||
}
|
||||
"trim" => {
|
||||
match try_parse_trim_strategy(value, &mut errors) {
|
||||
Ok(v) => config.table.trim = v,
|
||||
Err(e) => {
|
||||
// try_parse_trim_strategy() already adds its own errors
|
||||
errors.push(e);
|
||||
*value = config.table.trim.clone().into_value(span);
|
||||
}
|
||||
}
|
||||
}
|
||||
"show_empty" => {
|
||||
process_bool_config(value, &mut errors, &mut config.table.show_empty);
|
||||
}
|
||||
"abbreviated_row_count" => {
|
||||
match *value {
|
||||
Value::Int { val, .. } => {
|
||||
if val >= 0 {
|
||||
config.table.abbreviated_row_count = Some(val as usize);
|
||||
} else {
|
||||
report_invalid_value("should be an int unsigned", span, &mut errors);
|
||||
*value = config.table.abbreviated_row_count.map(|count| Value::int(count as i64, span)).unwrap_or(Value::nothing(span));
|
||||
}
|
||||
}
|
||||
Value::Nothing { .. } => {
|
||||
config.table.abbreviated_row_count = None;
|
||||
}
|
||||
_ => {
|
||||
report_invalid_value("should be an int", span, &mut errors);
|
||||
*value = config.table.abbreviated_row_count.map(|count| Value::int(count as i64, span)).unwrap_or(Value::nothing(span))
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
report_invalid_key(&[key, key2], span, &mut errors);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
true
|
||||
});
|
||||
} else {
|
||||
report_invalid_value("should be a record", span, &mut errors);
|
||||
*value = config.table.clone().into_value(span);
|
||||
}
|
||||
}
|
||||
"filesize" => {
|
||||
if let Value::Record { val, .. } = value {
|
||||
val.to_mut().retain_mut(|key2, value| {
|
||||
let span = value.span();
|
||||
match key2 {
|
||||
"metric" => {
|
||||
process_bool_config(value, &mut errors, &mut config.filesize.metric);
|
||||
}
|
||||
"format" => {
|
||||
if let Ok(v) = value.coerce_str() {
|
||||
config.filesize.format = v.to_lowercase();
|
||||
} else {
|
||||
report_invalid_value("should be a string", span, &mut errors);
|
||||
*value = Value::string(config.filesize.format.clone(), span);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
report_invalid_key(&[key, key2], span, &mut errors);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
true
|
||||
})
|
||||
} else {
|
||||
report_invalid_value("should be a record", span, &mut errors);
|
||||
*value = config.filesize.clone().into_value(span);
|
||||
}
|
||||
}
|
||||
"explore" => {
|
||||
if let Ok(map) = create_map(value) {
|
||||
config.explore = map;
|
||||
} else {
|
||||
report_invalid_value("should be a record", span, &mut errors);
|
||||
*value = config.explore.clone().into_value(span);
|
||||
}
|
||||
}
|
||||
// Misc. options
|
||||
"color_config" => {
|
||||
if let Ok(map) = create_map(value) {
|
||||
config.color_config = map;
|
||||
} else {
|
||||
report_invalid_value("should be a record", span, &mut errors);
|
||||
*value = config.color_config.clone().into_value(span);
|
||||
}
|
||||
}
|
||||
for (col, val) in record.iter() {
|
||||
let path = &mut path.push(col);
|
||||
match col.as_str() {
|
||||
"ls" => self.ls.update(val, path, errors),
|
||||
"rm" => self.rm.update(val, path, errors),
|
||||
"history" => self.history.update(val, path, errors),
|
||||
"completions" => self.completions.update(val, path, errors),
|
||||
"cursor_shape" => self.cursor_shape.update(val, path, errors),
|
||||
"table" => self.table.update(val, path, errors),
|
||||
"filesize" => self.filesize.update(val, path, errors),
|
||||
"explore" => self.explore.update(val, path, errors),
|
||||
"color_config" => self.color_config.update(val, path, errors),
|
||||
"use_grid_icons" => {
|
||||
// TODO: delete it after 0.99
|
||||
report_invalid_value(
|
||||
"`use_grid_icons` is deleted, you should delete the key, and use `grid -i` in such case.",
|
||||
span,
|
||||
&mut errors
|
||||
);
|
||||
errors.deprecated_option(path, "use `grid -i`", val.span());
|
||||
}
|
||||
"footer_mode" => {
|
||||
process_string_enum(
|
||||
&mut config.footer_mode,
|
||||
&[key],
|
||||
value,
|
||||
&mut errors
|
||||
);
|
||||
}
|
||||
"float_precision" => {
|
||||
process_int_config(value, &mut errors, &mut config.float_precision);
|
||||
}
|
||||
"use_ansi_coloring" => {
|
||||
process_bool_config(value, &mut errors, &mut config.use_ansi_coloring);
|
||||
}
|
||||
"edit_mode" => {
|
||||
process_string_enum(
|
||||
&mut config.edit_mode,
|
||||
&[key],
|
||||
value,
|
||||
&mut errors
|
||||
);
|
||||
}
|
||||
"shell_integration" => {
|
||||
if let Value::Record { val, .. } = value {
|
||||
val.to_mut().retain_mut(|key2, value| {
|
||||
let span = value.span();
|
||||
match key2 {
|
||||
"osc2" => {
|
||||
process_bool_config(value, &mut errors, &mut config.shell_integration.osc2);
|
||||
}
|
||||
"osc7" => {
|
||||
process_bool_config(value, &mut errors, &mut config.shell_integration.osc7);
|
||||
}
|
||||
"osc8" => {
|
||||
process_bool_config(value, &mut errors, &mut config.shell_integration.osc8);
|
||||
}
|
||||
"osc9_9" => {
|
||||
process_bool_config(value, &mut errors, &mut config.shell_integration.osc9_9);
|
||||
}
|
||||
"osc133" => {
|
||||
process_bool_config(value, &mut errors, &mut config.shell_integration.osc133);
|
||||
}
|
||||
"osc633" => {
|
||||
process_bool_config(value, &mut errors, &mut config.shell_integration.osc633);
|
||||
}
|
||||
"reset_application_mode" => {
|
||||
process_bool_config(value, &mut errors, &mut config.shell_integration.reset_application_mode);
|
||||
}
|
||||
_ => {
|
||||
report_invalid_key(&[key, key2], span, &mut errors);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
true
|
||||
})
|
||||
} else {
|
||||
report_invalid_value("boolean value is deprecated, should be a record. see `config nu --default`.", span, &mut errors);
|
||||
*value = config.shell_integration.into_value(span);
|
||||
}
|
||||
}
|
||||
"buffer_editor" => match value {
|
||||
"footer_mode" => self.footer_mode.update(val, path, errors),
|
||||
"float_precision" => self.float_precision.update(val, path, errors),
|
||||
"use_ansi_coloring" => self.use_ansi_coloring.update(val, path, errors),
|
||||
"edit_mode" => self.edit_mode.update(val, path, errors),
|
||||
"shell_integration" => self.shell_integration.update(val, path, errors),
|
||||
"buffer_editor" => match val {
|
||||
Value::Nothing { .. } | Value::String { .. } => {
|
||||
config.buffer_editor = value.clone();
|
||||
self.buffer_editor = val.clone();
|
||||
}
|
||||
Value::List { vals, .. }
|
||||
if vals.iter().all(|val| matches!(val, Value::String { .. })) =>
|
||||
{
|
||||
config.buffer_editor = value.clone();
|
||||
}
|
||||
_ => {
|
||||
report_invalid_value("should be a string, list<string>, or null", span, &mut errors);
|
||||
*value = config.buffer_editor.clone();
|
||||
self.buffer_editor = val.clone();
|
||||
}
|
||||
_ => errors.type_mismatch(
|
||||
path,
|
||||
Type::custom("string, list<string>, or nothing"),
|
||||
val,
|
||||
),
|
||||
},
|
||||
"show_banner" => {
|
||||
process_bool_config(value, &mut errors, &mut config.show_banner);
|
||||
}
|
||||
"display_errors" => {
|
||||
if let Value::Record { val, .. } = value {
|
||||
val.to_mut().retain_mut(|key2, value| {
|
||||
let span = value.span();
|
||||
match key2 {
|
||||
"exit_code" => {
|
||||
process_bool_config(value, &mut errors, &mut config.display_errors.exit_code);
|
||||
}
|
||||
"termination_signal" => {
|
||||
process_bool_config(value, &mut errors, &mut config.display_errors.termination_signal);
|
||||
}
|
||||
_ => {
|
||||
report_invalid_key(&[key, key2], span, &mut errors);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
true
|
||||
});
|
||||
} else {
|
||||
report_invalid_value("should be a record", span, &mut errors);
|
||||
*value = config.display_errors.into_value(span);
|
||||
}
|
||||
}
|
||||
"render_right_prompt_on_last_line" => {
|
||||
process_bool_config(value, &mut errors, &mut config.render_right_prompt_on_last_line);
|
||||
}
|
||||
"bracketed_paste" => {
|
||||
process_bool_config(value, &mut errors, &mut config.bracketed_paste);
|
||||
}
|
||||
"use_kitty_protocol" => {
|
||||
process_bool_config(value, &mut errors, &mut config.use_kitty_protocol);
|
||||
}
|
||||
"show_banner" => self.show_banner.update(val, path, errors),
|
||||
"display_errors" => self.display_errors.update(val, path, errors),
|
||||
"render_right_prompt_on_last_line" => self
|
||||
.render_right_prompt_on_last_line
|
||||
.update(val, path, errors),
|
||||
"bracketed_paste" => self.bracketed_paste.update(val, path, errors),
|
||||
"use_kitty_protocol" => self.use_kitty_protocol.update(val, path, errors),
|
||||
"highlight_resolved_externals" => {
|
||||
process_bool_config(value, &mut errors, &mut config.highlight_resolved_externals);
|
||||
self.highlight_resolved_externals.update(val, path, errors)
|
||||
}
|
||||
"plugins" => {
|
||||
if let Ok(map) = create_map(value) {
|
||||
config.plugins = map;
|
||||
} else {
|
||||
report_invalid_value("should be a record", span, &mut errors);
|
||||
*value = config.plugins.clone().into_value(span);
|
||||
}
|
||||
}
|
||||
"plugin_gc" => {
|
||||
config.plugin_gc.process(&[key], value, &mut errors);
|
||||
}
|
||||
"menus" => match create_menus(value) {
|
||||
Ok(map) => config.menus = map,
|
||||
Err(e) => {
|
||||
report_invalid_value("should be a valid list of menus", span, &mut errors);
|
||||
errors.push(e);
|
||||
*value = config.menus.clone().into_value(span);
|
||||
}
|
||||
"plugins" => self.plugins.update(val, path, errors),
|
||||
"plugin_gc" => self.plugin_gc.update(val, path, errors),
|
||||
"menus" => match Vec::from_value(val.clone()) {
|
||||
Ok(menus) => self.menus = menus,
|
||||
Err(err) => errors.error(err.into()),
|
||||
},
|
||||
"keybindings" => match create_keybindings(value) {
|
||||
Ok(keybindings) => config.keybindings = keybindings,
|
||||
Err(e) => {
|
||||
report_invalid_value("should be a valid keybindings list", span, &mut errors);
|
||||
errors.push(e);
|
||||
*value = config.keybindings.clone().into_value(span);
|
||||
}
|
||||
"keybindings" => match Vec::from_value(val.clone()) {
|
||||
Ok(keybindings) => self.keybindings = keybindings,
|
||||
Err(err) => errors.error(err.into()),
|
||||
},
|
||||
"hooks" => match create_hooks(value) {
|
||||
Ok(hooks) => config.hooks = hooks,
|
||||
Err(e) => {
|
||||
report_invalid_value("should be a valid hooks list", span, &mut errors);
|
||||
errors.push(e);
|
||||
*value = config.hooks.clone().into_value(span);
|
||||
}
|
||||
},
|
||||
"datetime_format" => {
|
||||
if let Value::Record { val, .. } = value {
|
||||
val.to_mut().retain_mut(|key2, value|
|
||||
{
|
||||
let span = value.span();
|
||||
match key2 {
|
||||
"normal" => {
|
||||
if let Ok(v) = value.coerce_string() {
|
||||
config.datetime_format.normal = Some(v);
|
||||
} else {
|
||||
report_invalid_value("should be a string", span, &mut errors);
|
||||
}
|
||||
}
|
||||
"table" => {
|
||||
if let Ok(v) = value.coerce_string() {
|
||||
config.datetime_format.table = Some(v);
|
||||
} else {
|
||||
report_invalid_value("should be a string", span, &mut errors);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
report_invalid_key(&[key, key2], span, &mut errors);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
true
|
||||
})
|
||||
} else {
|
||||
report_invalid_value("should be a record", span, &mut errors);
|
||||
*value = config.datetime_format.clone().into_value(span);
|
||||
}
|
||||
}
|
||||
"error_style" => {
|
||||
process_string_enum(
|
||||
&mut config.error_style,
|
||||
&[key],
|
||||
value,
|
||||
&mut errors
|
||||
);
|
||||
}
|
||||
"hooks" => self.hooks.update(val, path, errors),
|
||||
"datetime_format" => self.datetime_format.update(val, path, errors),
|
||||
"error_style" => self.error_style.update(val, path, errors),
|
||||
"recursion_limit" => {
|
||||
if let Value::Int { val, internal_span } = value {
|
||||
if val > &mut 1 {
|
||||
config.recursion_limit = *val;
|
||||
if let Ok(limit) = val.as_int() {
|
||||
if limit > 1 {
|
||||
self.recursion_limit = limit;
|
||||
} else {
|
||||
report_invalid_value("should be a integer greater than 1", span, &mut errors);
|
||||
*value = Value::Int { val: 50, internal_span: *internal_span };
|
||||
errors.invalid_value(path, "an int greater than 1", val);
|
||||
}
|
||||
} else {
|
||||
report_invalid_value("should be a integer greater than 1", span, &mut errors);
|
||||
*value = Value::Int { val: 50, internal_span: value.span() };
|
||||
errors.type_mismatch(path, Type::Int, val);
|
||||
}
|
||||
}
|
||||
// Catch all
|
||||
_ => {
|
||||
report_invalid_key(&[key], span, &mut errors);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
true
|
||||
});
|
||||
|
||||
// Return the config and the vec of errors.
|
||||
(
|
||||
config,
|
||||
if !errors.is_empty() {
|
||||
Some(ShellError::GenericError {
|
||||
error: "Config record contains invalid values or unknown settings".into(),
|
||||
msg: "".into(),
|
||||
span: None,
|
||||
help: None,
|
||||
inner: errors,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
},
|
||||
)
|
||||
_ => errors.unknown_option(path, val),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn update_from_value(&mut self, old: &Config, value: &Value) -> Option<ShellError> {
|
||||
// Current behaviour is that config errors are displayed, but do not prevent the rest
|
||||
// of the config from being updated (fields with errors are skipped/not updated).
|
||||
// Errors are simply collected one-by-one and wrapped into a ShellError variant at the end.
|
||||
let mut errors = ConfigErrors::new(old);
|
||||
let mut path = ConfigPath::new();
|
||||
|
||||
self.update(value, &mut path, &mut errors);
|
||||
|
||||
errors.into_shell_error()
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
use super::prelude::*;
|
||||
use super::{config_update_string_enum, prelude::*};
|
||||
use crate as nu_protocol;
|
||||
|
||||
#[derive(Clone, Copy, Debug, IntoValue, PartialEq, Eq, Serialize, Deserialize)]
|
||||
@ -14,7 +14,13 @@ impl FromStr for ErrorStyle {
|
||||
match s.to_ascii_lowercase().as_str() {
|
||||
"fancy" => Ok(Self::Fancy),
|
||||
"plain" => Ok(Self::Plain),
|
||||
_ => Err("expected either 'fancy' or 'plain'"),
|
||||
_ => Err("'fancy' or 'plain'"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UpdateFromValue for ErrorStyle {
|
||||
fn update(&mut self, value: &Value, path: &mut ConfigPath, errors: &mut ConfigErrors) {
|
||||
config_update_string_enum(self, value, path, errors)
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,5 @@
|
||||
use super::helper::{process_bool_config, report_invalid_key, report_invalid_value};
|
||||
use super::prelude::*;
|
||||
use crate as nu_protocol;
|
||||
use crate::ShellError;
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// Configures when plugins should be stopped if inactive
|
||||
@ -19,79 +17,28 @@ impl PluginGcConfigs {
|
||||
pub fn get(&self, plugin_name: &str) -> &PluginGcConfig {
|
||||
self.plugins.get(plugin_name).unwrap_or(&self.default)
|
||||
}
|
||||
|
||||
pub(super) fn process(
|
||||
&mut self,
|
||||
path: &[&str],
|
||||
value: &mut Value,
|
||||
errors: &mut Vec<ShellError>,
|
||||
) {
|
||||
if let Value::Record { val, .. } = value {
|
||||
// Handle resets to default if keys are missing
|
||||
if !val.contains("default") {
|
||||
self.default = PluginGcConfig::default();
|
||||
}
|
||||
if !val.contains("plugins") {
|
||||
self.plugins = HashMap::new();
|
||||
}
|
||||
|
||||
val.to_mut().retain_mut(|key, value| {
|
||||
let span = value.span();
|
||||
match key {
|
||||
"default" => {
|
||||
self.default
|
||||
.process(&join_path(path, &["default"]), value, errors)
|
||||
}
|
||||
"plugins" => process_plugins(
|
||||
&join_path(path, &["plugins"]),
|
||||
value,
|
||||
errors,
|
||||
&mut self.plugins,
|
||||
),
|
||||
_ => {
|
||||
report_invalid_key(&join_path(path, &[key]), span, errors);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
});
|
||||
} else {
|
||||
report_invalid_value("should be a record", value.span(), errors);
|
||||
*value = self.clone().into_value(value.span());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn process_plugins(
|
||||
path: &[&str],
|
||||
value: &mut Value,
|
||||
errors: &mut Vec<ShellError>,
|
||||
plugins: &mut HashMap<String, PluginGcConfig>,
|
||||
) {
|
||||
if let Value::Record { val, .. } = value {
|
||||
// Remove any plugin configs that aren't in the value
|
||||
plugins.retain(|key, _| val.contains(key));
|
||||
impl UpdateFromValue for PluginGcConfigs {
|
||||
fn update<'a>(
|
||||
&mut self,
|
||||
value: &'a Value,
|
||||
path: &mut ConfigPath<'a>,
|
||||
errors: &mut ConfigErrors,
|
||||
) {
|
||||
let Value::Record { val: record, .. } = value else {
|
||||
errors.type_mismatch(path, Type::record(), value);
|
||||
return;
|
||||
};
|
||||
|
||||
val.to_mut().retain_mut(|key, value| {
|
||||
if matches!(value, Value::Record { .. }) {
|
||||
plugins.entry(key.to_owned()).or_default().process(
|
||||
&join_path(path, &[key]),
|
||||
value,
|
||||
errors,
|
||||
);
|
||||
true
|
||||
} else {
|
||||
report_invalid_value("should be a record", value.span(), errors);
|
||||
if let Some(conf) = plugins.get(key) {
|
||||
// Reconstruct the value if it existed before
|
||||
*value = conf.clone().into_value(value.span());
|
||||
true
|
||||
} else {
|
||||
// Remove it if it didn't
|
||||
false
|
||||
}
|
||||
for (col, val) in record.iter() {
|
||||
let path = &mut path.push(col);
|
||||
match col.as_str() {
|
||||
"default" => self.default.update(val, path, errors),
|
||||
"plugins" => self.plugins.update(val, path, errors),
|
||||
_ => errors.unknown_option(path, val),
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -123,57 +70,43 @@ impl IntoValue for PluginGcConfig {
|
||||
}
|
||||
}
|
||||
|
||||
impl PluginGcConfig {
|
||||
fn process(&mut self, path: &[&str], value: &mut Value, errors: &mut Vec<ShellError>) {
|
||||
if let Value::Record { val, .. } = value {
|
||||
// Handle resets to default if keys are missing
|
||||
if !val.contains("enabled") {
|
||||
self.enabled = PluginGcConfig::default().enabled;
|
||||
}
|
||||
if !val.contains("stop_after") {
|
||||
self.stop_after = PluginGcConfig::default().stop_after;
|
||||
}
|
||||
impl UpdateFromValue for PluginGcConfig {
|
||||
fn update<'a>(
|
||||
&mut self,
|
||||
value: &'a Value,
|
||||
path: &mut ConfigPath<'a>,
|
||||
errors: &mut ConfigErrors,
|
||||
) {
|
||||
let Value::Record { val: record, .. } = value else {
|
||||
errors.type_mismatch(path, Type::record(), value);
|
||||
return;
|
||||
};
|
||||
|
||||
val.to_mut().retain_mut(|key, value| {
|
||||
let span = value.span();
|
||||
match key {
|
||||
"enabled" => process_bool_config(value, errors, &mut self.enabled),
|
||||
"stop_after" => match value {
|
||||
Value::Duration { val, .. } => {
|
||||
if *val >= 0 {
|
||||
self.stop_after = *val;
|
||||
} else {
|
||||
report_invalid_value("must not be negative", span, errors);
|
||||
*val = self.stop_after;
|
||||
}
|
||||
for (col, val) in record.iter() {
|
||||
let path = &mut path.push(col);
|
||||
match col.as_str() {
|
||||
"enabled" => self.enabled.update(val, path, errors),
|
||||
"stop_after" => {
|
||||
if let Ok(duration) = val.as_duration() {
|
||||
if duration >= 0 {
|
||||
self.stop_after = duration;
|
||||
} else {
|
||||
errors.invalid_value(path, "a non-negative duration", val);
|
||||
}
|
||||
_ => {
|
||||
report_invalid_value("should be a duration", span, errors);
|
||||
*value = Value::duration(self.stop_after, span);
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
report_invalid_key(&join_path(path, &[key]), span, errors);
|
||||
return false;
|
||||
} else {
|
||||
errors.type_mismatch(path, Type::Duration, val);
|
||||
}
|
||||
}
|
||||
true
|
||||
})
|
||||
} else {
|
||||
report_invalid_value("should be a record", value.span(), errors);
|
||||
*value = self.clone().into_value(value.span());
|
||||
_ => errors.unknown_option(path, val),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn join_path<'a>(a: &[&'a str], b: &[&'a str]) -> Vec<&'a str> {
|
||||
a.iter().copied().chain(b.iter().copied()).collect()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use nu_protocol::{record, Span};
|
||||
use crate::{record, Config, Span};
|
||||
|
||||
fn test_pair() -> (PluginGcConfigs, Value) {
|
||||
(
|
||||
@ -208,11 +141,12 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn process() {
|
||||
let (expected, mut input) = test_pair();
|
||||
let mut errors = vec![];
|
||||
fn update() {
|
||||
let (expected, input) = test_pair();
|
||||
let config = Config::default();
|
||||
let mut errors = ConfigErrors::new(&config);
|
||||
let mut result = PluginGcConfigs::default();
|
||||
result.process(&[], &mut input, &mut errors);
|
||||
result.update(&input, &mut ConfigPath::new(), &mut errors);
|
||||
assert!(errors.is_empty(), "errors: {errors:#?}");
|
||||
assert_eq!(expected, result);
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
pub use crate::{record, IntoValue, Span, Value};
|
||||
pub(super) use super::{error::ConfigErrors, ConfigPath, UpdateFromValue};
|
||||
pub use crate::{record, IntoValue, ShellError, Span, Type, Value};
|
||||
pub use serde::{Deserialize, Serialize};
|
||||
pub use std::str::FromStr;
|
||||
|
@ -1,9 +1,9 @@
|
||||
use super::{extract_value, prelude::*};
|
||||
use super::{config_update_string_enum, prelude::*};
|
||||
use crate as nu_protocol;
|
||||
use crate::ShellError;
|
||||
use crate::{engine::Closure, FromValue};
|
||||
|
||||
/// Definition of a parsed keybinding from the config object
|
||||
#[derive(Clone, Debug, IntoValue, Serialize, Deserialize)]
|
||||
#[derive(Clone, Debug, FromValue, IntoValue, Serialize, Deserialize)]
|
||||
pub struct ParsedKeybinding {
|
||||
pub modifier: Value,
|
||||
pub keycode: Value,
|
||||
@ -12,14 +12,14 @@ pub struct ParsedKeybinding {
|
||||
}
|
||||
|
||||
/// Definition of a parsed menu from the config object
|
||||
#[derive(Clone, Debug, IntoValue, Serialize, Deserialize)]
|
||||
#[derive(Clone, Debug, FromValue, IntoValue, Serialize, Deserialize)]
|
||||
pub struct ParsedMenu {
|
||||
pub name: Value,
|
||||
pub marker: Value,
|
||||
pub only_buffer_difference: Value,
|
||||
pub style: Value,
|
||||
pub r#type: Value,
|
||||
pub source: Value,
|
||||
pub source: Option<Closure>,
|
||||
}
|
||||
|
||||
/// Definition of a Nushell CursorShape (to be mapped to crossterm::cursor::CursorShape)
|
||||
@ -47,11 +47,17 @@ impl FromStr for NuCursorShape {
|
||||
"blink_block" => Ok(NuCursorShape::BlinkBlock),
|
||||
"blink_underscore" => Ok(NuCursorShape::BlinkUnderscore),
|
||||
"inherit" => Ok(NuCursorShape::Inherit),
|
||||
_ => Err("expected either 'line', 'block', 'underscore', 'blink_line', 'blink_block', 'blink_underscore' or 'inherit'"),
|
||||
_ => Err("'line', 'block', 'underscore', 'blink_line', 'blink_block', 'blink_underscore' or 'inherit'"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UpdateFromValue for NuCursorShape {
|
||||
fn update(&mut self, value: &Value, path: &mut ConfigPath, errors: &mut ConfigErrors) {
|
||||
config_update_string_enum(self, value, path, errors)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, IntoValue, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct CursorShapeConfig {
|
||||
pub emacs: NuCursorShape,
|
||||
@ -59,6 +65,30 @@ pub struct CursorShapeConfig {
|
||||
pub vi_normal: NuCursorShape,
|
||||
}
|
||||
|
||||
impl UpdateFromValue for CursorShapeConfig {
|
||||
fn update<'a>(
|
||||
&mut self,
|
||||
value: &'a Value,
|
||||
path: &mut ConfigPath<'a>,
|
||||
errors: &mut ConfigErrors,
|
||||
) {
|
||||
let Value::Record { val: record, .. } = value else {
|
||||
errors.type_mismatch(path, Type::record(), value);
|
||||
return;
|
||||
};
|
||||
|
||||
for (col, val) in record.iter() {
|
||||
let path = &mut path.push(col);
|
||||
match col.as_str() {
|
||||
"vi_insert" => self.vi_insert.update(val, path, errors),
|
||||
"vi_normal" => self.vi_normal.update(val, path, errors),
|
||||
"emacs" => self.emacs.update(val, path, errors),
|
||||
_ => errors.unknown_option(path, val),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, IntoValue, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum EditBindings {
|
||||
Vi,
|
||||
@ -73,89 +103,13 @@ impl FromStr for EditBindings {
|
||||
match s.to_ascii_lowercase().as_str() {
|
||||
"vi" => Ok(Self::Vi),
|
||||
"emacs" => Ok(Self::Emacs),
|
||||
_ => Err("expected either 'emacs' or 'vi'"),
|
||||
_ => Err("'emacs' or 'vi'"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses the config object to extract the strings that will compose a keybinding for reedline
|
||||
pub(super) fn create_keybindings(value: &Value) -> Result<Vec<ParsedKeybinding>, ShellError> {
|
||||
let span = value.span();
|
||||
match value {
|
||||
Value::Record { val, .. } => {
|
||||
// Finding the modifier value in the record
|
||||
let modifier = extract_value("modifier", val, span)?.clone();
|
||||
let keycode = extract_value("keycode", val, span)?.clone();
|
||||
let mode = extract_value("mode", val, span)?.clone();
|
||||
let event = extract_value("event", val, span)?.clone();
|
||||
|
||||
let keybinding = ParsedKeybinding {
|
||||
modifier,
|
||||
keycode,
|
||||
mode,
|
||||
event,
|
||||
};
|
||||
|
||||
// We return a menu to be able to do recursion on the same function
|
||||
Ok(vec![keybinding])
|
||||
}
|
||||
Value::List { vals, .. } => {
|
||||
let res = vals
|
||||
.iter()
|
||||
.map(create_keybindings)
|
||||
.collect::<Result<Vec<Vec<ParsedKeybinding>>, ShellError>>();
|
||||
|
||||
let res = res?
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.collect::<Vec<ParsedKeybinding>>();
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
_ => Ok(Vec::new()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses the config object to extract the strings that will compose a keybinding for reedline
|
||||
pub fn create_menus(value: &Value) -> Result<Vec<ParsedMenu>, ShellError> {
|
||||
let span = value.span();
|
||||
match value {
|
||||
Value::Record { val, .. } => {
|
||||
// Finding the modifier value in the record
|
||||
let name = extract_value("name", val, span)?.clone();
|
||||
let marker = extract_value("marker", val, span)?.clone();
|
||||
let only_buffer_difference =
|
||||
extract_value("only_buffer_difference", val, span)?.clone();
|
||||
let style = extract_value("style", val, span)?.clone();
|
||||
let r#type = extract_value("type", val, span)?.clone();
|
||||
|
||||
// Source is an optional value
|
||||
let source = match extract_value("source", val, span) {
|
||||
Ok(source) => source.clone(),
|
||||
Err(_) => Value::nothing(span),
|
||||
};
|
||||
|
||||
let menu = ParsedMenu {
|
||||
name,
|
||||
only_buffer_difference,
|
||||
marker,
|
||||
style,
|
||||
r#type,
|
||||
source,
|
||||
};
|
||||
|
||||
Ok(vec![menu])
|
||||
}
|
||||
Value::List { vals, .. } => {
|
||||
let res = vals
|
||||
.iter()
|
||||
.map(create_menus)
|
||||
.collect::<Result<Vec<Vec<ParsedMenu>>, ShellError>>();
|
||||
|
||||
let res = res?.into_iter().flatten().collect::<Vec<ParsedMenu>>();
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
_ => Ok(Vec::new()),
|
||||
impl UpdateFromValue for EditBindings {
|
||||
fn update(&mut self, value: &Value, path: &mut ConfigPath, errors: &mut ConfigErrors) {
|
||||
config_update_string_enum(self, value, path, errors)
|
||||
}
|
||||
}
|
||||
|
@ -14,3 +14,25 @@ impl Default for RmConfig {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UpdateFromValue for RmConfig {
|
||||
fn update<'a>(
|
||||
&mut self,
|
||||
value: &'a Value,
|
||||
path: &mut ConfigPath<'a>,
|
||||
errors: &mut ConfigErrors,
|
||||
) {
|
||||
let Value::Record { val: record, .. } = value else {
|
||||
errors.type_mismatch(path, Type::record(), value);
|
||||
return;
|
||||
};
|
||||
|
||||
for (col, val) in record.iter() {
|
||||
let path = &mut path.push(col);
|
||||
match col.as_str() {
|
||||
"always_trash" => self.always_trash.update(val, path, errors),
|
||||
_ => errors.unknown_option(path, val),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -26,3 +26,31 @@ impl Default for ShellIntegrationConfig {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UpdateFromValue for ShellIntegrationConfig {
|
||||
fn update<'a>(
|
||||
&mut self,
|
||||
value: &'a Value,
|
||||
path: &mut ConfigPath<'a>,
|
||||
errors: &mut ConfigErrors,
|
||||
) {
|
||||
let Value::Record { val: record, .. } = value else {
|
||||
errors.type_mismatch(path, Type::record(), value);
|
||||
return;
|
||||
};
|
||||
|
||||
for (col, val) in record.iter() {
|
||||
let path = &mut path.push(col);
|
||||
match col.as_str() {
|
||||
"osc2" => self.osc2.update(val, path, errors),
|
||||
"osc7" => self.osc7.update(val, path, errors),
|
||||
"osc8" => self.osc8.update(val, path, errors),
|
||||
"osc9_9" => self.osc9_9.update(val, path, errors),
|
||||
"osc133" => self.osc133.update(val, path, errors),
|
||||
"osc633" => self.osc633.update(val, path, errors),
|
||||
"reset_application_mode" => self.reset_application_mode.update(val, path, errors),
|
||||
_ => errors.unknown_option(path, val),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
use super::prelude::*;
|
||||
use super::{config_update_string_enum, prelude::*};
|
||||
use crate as nu_protocol;
|
||||
use crate::ShellError;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, IntoValue, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum TableMode {
|
||||
@ -45,11 +44,17 @@ impl FromStr for TableMode {
|
||||
"restructured" => Ok(Self::Restructured),
|
||||
"ascii_rounded" => Ok(Self::AsciiRounded),
|
||||
"basic_compact" => Ok(Self::BasicCompact),
|
||||
_ => Err("expected either 'basic', 'thin', 'light', 'compact', 'with_love', 'compact_double', 'rounded', 'reinforced', 'heavy', 'none', 'psql', 'markdown', 'dots', 'restructured', 'ascii_rounded', or 'basic_compact'"),
|
||||
_ => Err("'basic', 'thin', 'light', 'compact', 'with_love', 'compact_double', 'rounded', 'reinforced', 'heavy', 'none', 'psql', 'markdown', 'dots', 'restructured', 'ascii_rounded', or 'basic_compact'"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UpdateFromValue for TableMode {
|
||||
fn update(&mut self, value: &Value, path: &mut ConfigPath, errors: &mut ConfigErrors) {
|
||||
config_update_string_enum(self, value, path, errors)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum FooterMode {
|
||||
/// Never show the footer
|
||||
@ -70,13 +75,30 @@ impl FromStr for FooterMode {
|
||||
"always" => Ok(FooterMode::Always),
|
||||
"never" => Ok(FooterMode::Never),
|
||||
"auto" => Ok(FooterMode::Auto),
|
||||
x => {
|
||||
if let Ok(count) = x.parse() {
|
||||
Ok(FooterMode::RowCount(count))
|
||||
_ => Err("'never', 'always', 'auto', or int"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UpdateFromValue for FooterMode {
|
||||
fn update(&mut self, value: &Value, path: &mut ConfigPath, errors: &mut ConfigErrors) {
|
||||
match value {
|
||||
Value::String { val, .. } => match val.parse() {
|
||||
Ok(val) => *self = val,
|
||||
Err(err) => errors.invalid_value(path, err.to_string(), value),
|
||||
},
|
||||
&Value::Int { val, .. } => {
|
||||
if val >= 0 {
|
||||
*self = Self::RowCount(val as u64);
|
||||
} else {
|
||||
Err("expected either 'never', 'always', 'auto' or a row count")
|
||||
errors.invalid_value(path, "a non-negative integer", value);
|
||||
}
|
||||
}
|
||||
_ => errors.type_mismatch(
|
||||
path,
|
||||
Type::custom("'never', 'always', 'auto', or int"),
|
||||
value,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -87,7 +109,7 @@ impl IntoValue for FooterMode {
|
||||
FooterMode::Always => "always".into_value(span),
|
||||
FooterMode::Never => "never".into_value(span),
|
||||
FooterMode::Auto => "auto".into_value(span),
|
||||
FooterMode::RowCount(c) => c.to_string().into_value(span),
|
||||
FooterMode::RowCount(c) => (c as i64).into_value(span),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -110,11 +132,17 @@ impl FromStr for TableIndexMode {
|
||||
"always" => Ok(TableIndexMode::Always),
|
||||
"never" => Ok(TableIndexMode::Never),
|
||||
"auto" => Ok(TableIndexMode::Auto),
|
||||
_ => Err("expected either 'never', 'always' or 'auto'"),
|
||||
_ => Err("'never', 'always' or 'auto'"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UpdateFromValue for TableIndexMode {
|
||||
fn update(&mut self, value: &Value, path: &mut ConfigPath, errors: &mut ConfigErrors) {
|
||||
config_update_string_enum(self, value, path, errors)
|
||||
}
|
||||
}
|
||||
|
||||
/// A Table view configuration, for a situation where
|
||||
/// we need to limit cell width in order to adjust for a terminal size.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
@ -159,93 +187,6 @@ impl Default for TrimStrategy {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn try_parse_trim_strategy(
|
||||
value: &Value,
|
||||
errors: &mut Vec<ShellError>,
|
||||
) -> Result<TrimStrategy, ShellError> {
|
||||
let map = value.as_record().map_err(|e| ShellError::GenericError {
|
||||
error: "Error while applying config changes".into(),
|
||||
msg: "$env.config.table.trim is not a record".into(),
|
||||
span: Some(value.span()),
|
||||
help: Some("Please consult the documentation for configuring Nushell.".into()),
|
||||
inner: vec![e],
|
||||
})?;
|
||||
|
||||
let mut methodology = match map.get("methodology") {
|
||||
Some(value) => match try_parse_trim_methodology(value) {
|
||||
Some(methodology) => methodology,
|
||||
None => return Ok(TrimStrategy::default()),
|
||||
},
|
||||
None => {
|
||||
errors.push(ShellError::GenericError {
|
||||
error: "Error while applying config changes".into(),
|
||||
msg: "$env.config.table.trim.methodology was not provided".into(),
|
||||
span: Some(value.span()),
|
||||
help: Some("Please consult the documentation for configuring Nushell.".into()),
|
||||
inner: vec![],
|
||||
});
|
||||
return Ok(TrimStrategy::default());
|
||||
}
|
||||
};
|
||||
|
||||
match &mut methodology {
|
||||
TrimStrategy::Wrap { try_to_keep_words } => {
|
||||
if let Some(value) = map.get("wrapping_try_keep_words") {
|
||||
if let Ok(b) = value.as_bool() {
|
||||
*try_to_keep_words = b;
|
||||
} else {
|
||||
errors.push(ShellError::GenericError {
|
||||
error: "Error while applying config changes".into(),
|
||||
msg: "$env.config.table.trim.wrapping_try_keep_words is not a bool".into(),
|
||||
span: Some(value.span()),
|
||||
help: Some(
|
||||
"Please consult the documentation for configuring Nushell.".into(),
|
||||
),
|
||||
inner: vec![],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
TrimStrategy::Truncate { suffix } => {
|
||||
if let Some(value) = map.get("truncating_suffix") {
|
||||
if let Ok(v) = value.coerce_string() {
|
||||
*suffix = Some(v);
|
||||
} else {
|
||||
errors.push(ShellError::GenericError {
|
||||
error: "Error while applying config changes".into(),
|
||||
msg: "$env.config.table.trim.truncating_suffix is not a string".into(),
|
||||
span: Some(value.span()),
|
||||
help: Some(
|
||||
"Please consult the documentation for configuring Nushell.".into(),
|
||||
),
|
||||
inner: vec![],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(methodology)
|
||||
}
|
||||
|
||||
fn try_parse_trim_methodology(value: &Value) -> Option<TrimStrategy> {
|
||||
if let Ok(value) = value.coerce_str() {
|
||||
match value.to_lowercase().as_str() {
|
||||
"wrapping" => {
|
||||
return Some(TrimStrategy::Wrap {
|
||||
try_to_keep_words: false,
|
||||
});
|
||||
}
|
||||
"truncating" => return Some(TrimStrategy::Truncate { suffix: None }),
|
||||
_ => eprintln!("unrecognized $config.table.trim.methodology value; expected either 'truncating' or 'wrapping'"),
|
||||
}
|
||||
} else {
|
||||
eprintln!("$env.config.table.trim.methodology is not a string")
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
impl IntoValue for TrimStrategy {
|
||||
fn into_value(self, span: Span) -> Value {
|
||||
match self {
|
||||
@ -266,6 +207,70 @@ impl IntoValue for TrimStrategy {
|
||||
}
|
||||
}
|
||||
|
||||
impl UpdateFromValue for TrimStrategy {
|
||||
fn update<'a>(
|
||||
&mut self,
|
||||
value: &'a Value,
|
||||
path: &mut ConfigPath<'a>,
|
||||
errors: &mut ConfigErrors,
|
||||
) {
|
||||
let Value::Record { val: record, .. } = value else {
|
||||
errors.type_mismatch(path, Type::record(), value);
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(methodology) = record.get("methodology") else {
|
||||
errors.missing_column(path, "methodology", value.span());
|
||||
return;
|
||||
};
|
||||
|
||||
match methodology.as_str() {
|
||||
Ok("wrapping") => {
|
||||
let mut try_to_keep_words = if let &mut Self::Wrap { try_to_keep_words } = self {
|
||||
try_to_keep_words
|
||||
} else {
|
||||
false
|
||||
};
|
||||
for (col, val) in record.iter() {
|
||||
let path = &mut path.push(col);
|
||||
match col.as_str() {
|
||||
"wrapping_try_keep_words" => try_to_keep_words.update(val, path, errors),
|
||||
"methodology" | "truncating_suffix" => (),
|
||||
_ => errors.unknown_option(path, val),
|
||||
}
|
||||
}
|
||||
*self = Self::Wrap { try_to_keep_words };
|
||||
}
|
||||
Ok("truncating") => {
|
||||
let mut suffix = if let Self::Truncate { suffix } = self {
|
||||
suffix.take()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
for (col, val) in record.iter() {
|
||||
let path = &mut path.push(col);
|
||||
match col.as_str() {
|
||||
"truncating_suffix" => match val {
|
||||
Value::Nothing { .. } => suffix = None,
|
||||
Value::String { val, .. } => suffix = Some(val.clone()),
|
||||
_ => errors.type_mismatch(path, Type::String, val),
|
||||
},
|
||||
"methodology" | "wrapping_try_keep_words" => (),
|
||||
_ => errors.unknown_option(path, val),
|
||||
}
|
||||
}
|
||||
*self = Self::Truncate { suffix };
|
||||
}
|
||||
Ok(_) => errors.invalid_value(
|
||||
&path.push("methodology"),
|
||||
"'wrapping' or 'truncating'",
|
||||
methodology,
|
||||
),
|
||||
Err(_) => errors.type_mismatch(&path.push("methodology"), Type::String, methodology),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct TableIndent {
|
||||
pub left: usize,
|
||||
@ -288,6 +293,37 @@ impl Default for TableIndent {
|
||||
}
|
||||
}
|
||||
|
||||
impl UpdateFromValue for TableIndent {
|
||||
fn update<'a>(
|
||||
&mut self,
|
||||
value: &'a Value,
|
||||
path: &mut ConfigPath<'a>,
|
||||
errors: &mut ConfigErrors,
|
||||
) {
|
||||
match value {
|
||||
&Value::Int { val, .. } => {
|
||||
if let Ok(val) = val.try_into() {
|
||||
self.left = val;
|
||||
self.right = val;
|
||||
} else {
|
||||
errors.invalid_value(path, "a non-negative integer", value);
|
||||
}
|
||||
}
|
||||
Value::Record { val: record, .. } => {
|
||||
for (col, val) in record.iter() {
|
||||
let path = &mut path.push(col);
|
||||
match col.as_str() {
|
||||
"left" => self.left.update(val, path, errors),
|
||||
"right" => self.right.update(val, path, errors),
|
||||
_ => errors.unknown_option(path, val),
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => errors.type_mismatch(path, Type::custom("int or record"), value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct TableConfig {
|
||||
pub mode: TableMode,
|
||||
@ -332,3 +368,41 @@ impl Default for TableConfig {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UpdateFromValue for TableConfig {
|
||||
fn update<'a>(
|
||||
&mut self,
|
||||
value: &'a Value,
|
||||
path: &mut ConfigPath<'a>,
|
||||
errors: &mut ConfigErrors,
|
||||
) {
|
||||
let Value::Record { val: record, .. } = value else {
|
||||
errors.type_mismatch(path, Type::record(), value);
|
||||
return;
|
||||
};
|
||||
|
||||
for (col, val) in record.iter() {
|
||||
let path = &mut path.push(col);
|
||||
match col.as_str() {
|
||||
"mode" => self.mode.update(val, path, errors),
|
||||
"index_mode" => self.index_mode.update(val, path, errors),
|
||||
"show_empty" => self.show_empty.update(val, path, errors),
|
||||
"trim" => self.trim.update(val, path, errors),
|
||||
"header_on_separator" => self.header_on_separator.update(val, path, errors),
|
||||
"padding" => self.padding.update(val, path, errors),
|
||||
"abbreviated_row_count" => match val {
|
||||
Value::Nothing { .. } => self.abbreviated_row_count = None,
|
||||
&Value::Int { val: count, .. } => {
|
||||
if let Ok(count) = count.try_into() {
|
||||
self.abbreviated_row_count = Some(count);
|
||||
} else {
|
||||
errors.invalid_value(path, "a non-negative integer", val);
|
||||
}
|
||||
}
|
||||
_ => errors.type_mismatch(path, Type::custom("int or nothing"), val),
|
||||
},
|
||||
_ => errors.unknown_option(path, val),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ use crate::{
|
||||
ArgumentStack, EngineState, ErrorHandlerStack, Redirection, StackCallArgGuard,
|
||||
StackCollectValueGuard, StackIoGuard, StackOutDest, DEFAULT_OVERLAY_NAME,
|
||||
},
|
||||
Config, OutDest, ShellError, Span, Value, VarId, ENV_VARIABLE_ID, NU_VARIABLE_ID,
|
||||
Config, IntoValue, OutDest, ShellError, Span, Value, VarId, ENV_VARIABLE_ID, NU_VARIABLE_ID,
|
||||
};
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
@ -211,12 +211,13 @@ impl Stack {
|
||||
///
|
||||
/// The config will be updated with successfully parsed values even if an error occurs.
|
||||
pub fn update_config(&mut self, engine_state: &EngineState) -> Result<(), ShellError> {
|
||||
if let Some(mut config) = self.get_env_var(engine_state, "config").cloned() {
|
||||
let existing_config = self.get_config(engine_state);
|
||||
let (new_config, error) = config.parse_as_config(&existing_config);
|
||||
self.config = Some(new_config.into());
|
||||
if let Some(value) = self.get_env_var(engine_state, "config") {
|
||||
let old = self.get_config(engine_state);
|
||||
let mut config = (*old).clone();
|
||||
let error = config.update_from_value(&old, value);
|
||||
// The config value is modified by the update, so we should add it again
|
||||
self.add_env_var("config".into(), config);
|
||||
self.add_env_var("config".into(), config.clone().into_value(value.span()));
|
||||
self.config = Some(config.into());
|
||||
match error {
|
||||
None => Ok(()),
|
||||
Some(err) => Err(err),
|
||||
|
@ -15,8 +15,8 @@ use thiserror::Error;
|
||||
/// forwards most methods, except for `.source_code()`, which we provide.
|
||||
#[derive(Error)]
|
||||
#[error("{0}")]
|
||||
pub struct CliError<'src>(
|
||||
pub &'src (dyn miette::Diagnostic + Send + Sync + 'static),
|
||||
struct CliError<'src>(
|
||||
pub &'src dyn miette::Diagnostic,
|
||||
pub &'src StateWorkingSet<'src>,
|
||||
);
|
||||
|
||||
@ -48,10 +48,7 @@ pub fn report_compile_error(working_set: &StateWorkingSet, error: &CompileError)
|
||||
report_error(working_set, error);
|
||||
}
|
||||
|
||||
fn report_error(
|
||||
working_set: &StateWorkingSet,
|
||||
error: &(dyn miette::Diagnostic + Send + Sync + 'static),
|
||||
) {
|
||||
fn report_error(working_set: &StateWorkingSet, error: &dyn miette::Diagnostic) {
|
||||
eprintln!("Error: {:?}", CliError(error, working_set));
|
||||
// reset vt processing, aka ansi because illbehaved externals can break it
|
||||
#[cfg(windows)]
|
||||
@ -60,10 +57,7 @@ fn report_error(
|
||||
}
|
||||
}
|
||||
|
||||
fn report_warning(
|
||||
working_set: &StateWorkingSet,
|
||||
error: &(dyn miette::Diagnostic + Send + Sync + 'static),
|
||||
) {
|
||||
fn report_warning(working_set: &StateWorkingSet, error: &dyn miette::Diagnostic) {
|
||||
eprintln!("Warning: {:?}", CliError(error, working_set));
|
||||
// reset vt processing, aka ansi because illbehaved externals can break it
|
||||
#[cfg(windows)]
|
||||
|
56
crates/nu-protocol/src/errors/config_error.rs
Normal file
56
crates/nu-protocol/src/errors/config_error.rs
Normal file
@ -0,0 +1,56 @@
|
||||
use crate::{ShellError, Span, Type};
|
||||
use miette::Diagnostic;
|
||||
use thiserror::Error;
|
||||
|
||||
/// The errors that may occur when updating the config
|
||||
#[derive(Clone, Debug, PartialEq, Error, Diagnostic)]
|
||||
pub enum ConfigError {
|
||||
#[error("Type mismatch at {path}")]
|
||||
#[diagnostic(code(nu::shell::type_mismatch))]
|
||||
TypeMismatch {
|
||||
path: String,
|
||||
expected: Type,
|
||||
actual: Type,
|
||||
#[label = "expected {expected}, but got {actual}"]
|
||||
span: Span,
|
||||
},
|
||||
#[error("Invalid value for {path}")]
|
||||
#[diagnostic(code(nu::shell::invalid_value))]
|
||||
InvalidValue {
|
||||
path: String,
|
||||
valid: String,
|
||||
actual: String,
|
||||
#[label = "expected {valid}, but got {actual}"]
|
||||
span: Span,
|
||||
},
|
||||
#[error("Unknown config option: {path}")]
|
||||
#[diagnostic(code(nu::shell::unknown_config_option))]
|
||||
UnknownOption {
|
||||
path: String,
|
||||
#[label("remove this")]
|
||||
span: Span,
|
||||
},
|
||||
#[error("{path} requires a '{column}' column")]
|
||||
#[diagnostic(code(nu::shell::missing_required_column))]
|
||||
MissingRequiredColumn {
|
||||
path: String,
|
||||
column: &'static str,
|
||||
#[label("has no '{column}' column")]
|
||||
span: Span,
|
||||
},
|
||||
#[error("{path} is deprecated")]
|
||||
#[diagnostic(
|
||||
code(nu::shell::deprecated_config_option),
|
||||
help("please {suggestion} instead")
|
||||
)]
|
||||
Deprecated {
|
||||
path: String,
|
||||
suggestion: &'static str,
|
||||
#[label("deprecated")]
|
||||
span: Span,
|
||||
},
|
||||
// TODO: remove this
|
||||
#[error(transparent)]
|
||||
#[diagnostic(transparent)]
|
||||
ShellError(#[from] ShellError),
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
pub mod cli_error;
|
||||
mod compile_error;
|
||||
mod config_error;
|
||||
mod labeled_error;
|
||||
mod parse_error;
|
||||
mod parse_warning;
|
||||
@ -10,6 +11,7 @@ pub use cli_error::{
|
||||
report_shell_warning,
|
||||
};
|
||||
pub use compile_error::CompileError;
|
||||
pub use config_error::ConfigError;
|
||||
pub use labeled_error::{ErrorLabel, LabeledError};
|
||||
pub use parse_error::{DidYouMean, ParseError};
|
||||
pub use parse_warning::ParseWarning;
|
||||
|
@ -1,13 +1,12 @@
|
||||
use crate::{
|
||||
ast::Operator, engine::StateWorkingSet, format_shell_error, record, ConfigError, LabeledError,
|
||||
ParseError, Span, Spanned, Type, Value,
|
||||
};
|
||||
use miette::Diagnostic;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{io, num::NonZeroI32};
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::{
|
||||
ast::Operator, engine::StateWorkingSet, format_shell_error, record, LabeledError, ParseError,
|
||||
Span, Spanned, Value,
|
||||
};
|
||||
|
||||
/// The fundamental error type for the evaluation engine. These cases represent different kinds of errors
|
||||
/// the evaluator might face, along with helpful spans to label. An error renderer will take this error value
|
||||
/// and pass it into an error viewer to display to the user.
|
||||
@ -108,6 +107,34 @@ pub enum ShellError {
|
||||
span: Span,
|
||||
},
|
||||
|
||||
/// A value's type did not match the expected type.
|
||||
///
|
||||
/// ## Resolution
|
||||
///
|
||||
/// Convert the value to the correct type or provide a value of the correct type.
|
||||
#[error("Type mismatch")]
|
||||
#[diagnostic(code(nu::shell::type_mismatch))]
|
||||
RuntimeTypeMismatch {
|
||||
expected: Type,
|
||||
actual: Type,
|
||||
#[label = "expected {expected}, but got {actual}"]
|
||||
span: Span,
|
||||
},
|
||||
|
||||
/// A value had the correct type but is otherwise invalid.
|
||||
///
|
||||
/// ## Resolution
|
||||
///
|
||||
/// Ensure the value meets the criteria in the error message.
|
||||
#[error("Invalid value")]
|
||||
#[diagnostic(code(nu::shell::invalid_value))]
|
||||
InvalidValue {
|
||||
valid: String,
|
||||
actual: String,
|
||||
#[label = "expected {valid}, but got {actual}"]
|
||||
span: Span,
|
||||
},
|
||||
|
||||
/// A command received an argument with correct type but incorrect value.
|
||||
///
|
||||
/// ## Resolution
|
||||
@ -1085,30 +1112,28 @@ pub enum ShellError {
|
||||
span: Span,
|
||||
},
|
||||
|
||||
/// The value given for this configuration is not supported.
|
||||
/// Failed to update the config due to one or more errors.
|
||||
///
|
||||
/// ## Resolution
|
||||
///
|
||||
/// Refer to the specific error message for details and convert values as needed.
|
||||
#[error("Unsupported config value")]
|
||||
#[diagnostic(code(nu::shell::unsupported_config_value))]
|
||||
UnsupportedConfigValue {
|
||||
expected: String,
|
||||
value: String,
|
||||
#[label("expected {expected}, got {value}")]
|
||||
span: Span,
|
||||
/// Refer to the error messages for specific details.
|
||||
#[error("Encountered {} error(s) when updating config", errors.len())]
|
||||
#[diagnostic(code(nu::shell::invalid_config))]
|
||||
InvalidConfig {
|
||||
#[related]
|
||||
errors: Vec<ConfigError>,
|
||||
},
|
||||
|
||||
/// An expected configuration value is not present.
|
||||
/// A value was missing a required column.
|
||||
///
|
||||
/// ## Resolution
|
||||
///
|
||||
/// Refer to the specific error message and add the configuration value to your config file as needed.
|
||||
#[error("Missing config value")]
|
||||
#[diagnostic(code(nu::shell::missing_config_value))]
|
||||
MissingConfigValue {
|
||||
missing_value: String,
|
||||
#[label("missing {missing_value}")]
|
||||
/// Make sure the value has the required column.
|
||||
#[error("Value is missing a required '{column}' column")]
|
||||
#[diagnostic(code(nu::shell::missing_required_column))]
|
||||
MissingRequiredColumn {
|
||||
column: &'static str,
|
||||
#[label("has no '{column}' column")]
|
||||
span: Span,
|
||||
},
|
||||
|
||||
|
@ -48,6 +48,10 @@ impl Type {
|
||||
Self::Table([].into())
|
||||
}
|
||||
|
||||
pub fn custom(name: impl Into<Box<str>>) -> Self {
|
||||
Self::Custom(name.into())
|
||||
}
|
||||
|
||||
pub fn is_subtype(&self, other: &Type) -> bool {
|
||||
// Structural subtyping
|
||||
let is_subtype_collection = |this: &[(String, Type)], that: &[(String, Type)]| {
|
||||
|
@ -52,7 +52,7 @@ fn config_add_unsupported_key() {
|
||||
|
||||
assert!(actual
|
||||
.err
|
||||
.contains("$env.config.foo is an unknown config setting"));
|
||||
.contains("Unknown config option: $env.config.foo"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -61,7 +61,7 @@ fn config_add_unsupported_type() {
|
||||
r#"$env.config.ls = '' "#,
|
||||
r#";"#]));
|
||||
|
||||
assert!(actual.err.contains("should be a record"));
|
||||
assert!(actual.err.contains("Type mismatch"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -70,12 +70,8 @@ fn config_add_unsupported_value() {
|
||||
r#"$env.config.history.file_format = ''"#,
|
||||
r#";"#]));
|
||||
|
||||
assert!(actual
|
||||
.err
|
||||
.contains("unrecognized $env.config.history.file_format option ''"));
|
||||
assert!(actual
|
||||
.err
|
||||
.contains("expected either 'sqlite' or 'plaintext'"));
|
||||
assert!(actual.err.contains("Invalid value"));
|
||||
assert!(actual.err.contains("expected 'sqlite' or 'plaintext'"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -2,10 +2,9 @@ use nu_cmd_base::hook::{eval_env_change_hook, eval_hook};
|
||||
use nu_engine::eval_block;
|
||||
use nu_parser::parse;
|
||||
use nu_protocol::{
|
||||
cli_error::CliError,
|
||||
debugger::WithoutDebug,
|
||||
engine::{EngineState, Stack, StateWorkingSet},
|
||||
PipelineData, Value,
|
||||
report_parse_error, report_shell_error, PipelineData, ShellError, Value,
|
||||
};
|
||||
use nu_std::load_standard_library;
|
||||
use std::{
|
||||
@ -209,20 +208,13 @@ pub fn chop() {
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
fn outcome_err(
|
||||
engine_state: &EngineState,
|
||||
error: &(dyn miette::Diagnostic + Send + Sync + 'static),
|
||||
) -> ! {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
|
||||
eprintln!("Error: {:?}", CliError(error, &working_set));
|
||||
|
||||
fn outcome_err(engine_state: &EngineState, error: &ShellError) -> ! {
|
||||
report_shell_error(engine_state, error);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
fn outcome_ok(msg: String) -> ! {
|
||||
println!("{msg}");
|
||||
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
@ -320,7 +312,8 @@ pub fn nu_repl() {
|
||||
);
|
||||
|
||||
if let Some(err) = working_set.parse_errors.first() {
|
||||
outcome_err(&engine_state, err);
|
||||
report_parse_error(&working_set, err);
|
||||
std::process::exit(1);
|
||||
}
|
||||
(block, working_set.render())
|
||||
};
|
||||
|
@ -441,7 +441,7 @@ fn err_hook_wrong_env_type_1() {
|
||||
let actual_repl = nu!(nu_repl_code(inp));
|
||||
dbg!(&actual_repl.err);
|
||||
|
||||
assert!(actual_repl.err.contains("unsupported_config_value"));
|
||||
assert!(actual_repl.err.contains("Type mismatch"));
|
||||
assert_eq!(actual_repl.out, "");
|
||||
}
|
||||
|
||||
@ -458,7 +458,7 @@ fn err_hook_wrong_env_type_2() {
|
||||
|
||||
let actual_repl = nu!(nu_repl_code(inp));
|
||||
|
||||
assert!(actual_repl.err.contains("type_mismatch"));
|
||||
assert!(actual_repl.err.contains("Type mismatch"));
|
||||
assert_eq!(actual_repl.out, "");
|
||||
}
|
||||
|
||||
@ -480,7 +480,7 @@ fn err_hook_wrong_env_type_3() {
|
||||
|
||||
let actual_repl = nu!(nu_repl_code(inp));
|
||||
|
||||
assert!(actual_repl.err.contains("unsupported_config_value"));
|
||||
assert!(actual_repl.err.contains("Type mismatch"));
|
||||
assert_eq!(actual_repl.out, "");
|
||||
}
|
||||
|
||||
@ -503,7 +503,7 @@ fn err_hook_non_boolean_condition_output() {
|
||||
|
||||
let actual_repl = nu!(nu_repl_code(inp));
|
||||
|
||||
assert!(actual_repl.err.contains("unsupported_config_value"));
|
||||
assert!(actual_repl.err.contains("Type mismatch"));
|
||||
assert_eq!(actual_repl.out, "");
|
||||
}
|
||||
|
||||
@ -526,7 +526,7 @@ fn err_hook_non_condition_not_a_block() {
|
||||
|
||||
let actual_repl = nu!(nu_repl_code(inp));
|
||||
|
||||
assert!(actual_repl.err.contains("unsupported_config_value"));
|
||||
assert!(actual_repl.err.contains("Type mismatch"));
|
||||
assert_eq!(actual_repl.out, "");
|
||||
}
|
||||
|
||||
@ -548,7 +548,7 @@ fn err_hook_parse_error() {
|
||||
|
||||
let actual_repl = nu!(nu_repl_code(inp));
|
||||
|
||||
assert!(actual_repl.err.contains("unsupported_config_value"));
|
||||
assert!(actual_repl.err.contains("source code has errors"));
|
||||
assert_eq!(actual_repl.out, "");
|
||||
}
|
||||
|
||||
|
@ -119,7 +119,7 @@ fn mutate_nu_config_plugin() -> TestResult {
|
||||
|
||||
#[test]
|
||||
fn reject_nu_config_plugin_non_record() -> TestResult {
|
||||
fail_test(r#"$env.config.plugins = 5"#, "should be a record")
|
||||
fail_test(r#"$env.config.plugins = 5"#, "Type mismatch")
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -151,7 +151,7 @@ fn mutate_nu_config_plugin_gc_default_stop_after_negative() -> TestResult {
|
||||
$env.config.plugin_gc.default.stop_after = -1sec
|
||||
$env.config.plugin_gc.default.stop_after
|
||||
"#,
|
||||
"must not be negative",
|
||||
"expected a non-negative duration",
|
||||
)
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user