Name the Value conversion functions more clearly (#11851)

# Description
This PR renames the conversion functions on `Value` to be more consistent.
It follows the Rust [API guidelines](https://rust-lang.github.io/api-guidelines/naming.html#ad-hoc-conversions-follow-as_-to_-into_-conventions-c-conv) for ad-hoc conversions.
The conversion functions on `Value` now come in a few forms:
- `coerce_{type}` takes a `&Value` and attempts to convert the value to
`type` (e.g., `i64` are converted to `f64`). This is the old behavior of
some of the `as_{type}` functions -- these functions have simply been
renamed to better reflect what they do.
- The new `as_{type}` functions take a `&Value` and returns an `Ok`
result only if the value is of `type` (no conversion is attempted). The
returned value will be borrowed if `type` is non-`Copy`, otherwise an
owned value is returned.
- `into_{type}` exists for non-`Copy` types, but otherwise does not
attempt conversion just like `as_type`. It takes an owned `Value` and
always returns an owned result.
- `coerce_into_{type}` has the same relationship with `coerce_{type}` as
`into_{type}` does with `as_{type}`.
- `to_{kind}_string`: conversion to different string formats (debug,
abbreviated, etc.). Only two of the old string conversion functions were
removed, the rest have been renamed only.
- `to_{type}`: other conversion functions. Currently, only `to_path`
exists. (And `to_string` through `Display`.)

This table summaries the above:
| Form | Cost | Input Ownership | Output Ownership | Converts `Value`
case/`type` |
| ---------------------------- | ----- | --------------- |
---------------- | -------- |
| `as_{type}` | Cheap | Borrowed | Borrowed/Owned | No |
| `into_{type}` | Cheap | Owned | Owned | No |
| `coerce_{type}` | Cheap | Borrowed | Borrowed/Owned | Yes |
| `coerce_into_{type}` | Cheap | Owned | Owned | Yes |
| `to_{kind}_string` | Expensive | Borrowed | Owned | Yes |
| `to_{type}` | Expensive | Borrowed | Owned | Yes |

# User-Facing Changes
Breaking API change for `Value` in `nu-protocol` which is exposed as
part of the plugin API.
This commit is contained in:
Ian Manske
2024-02-17 18:14:16 +00:00
committed by GitHub
parent 360ebeb0bc
commit 1c49ca503a
117 changed files with 903 additions and 745 deletions

View File

@ -70,11 +70,12 @@ impl Command for Commandline {
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
if let Some(cmd) = call.opt::<Value>(engine_state, stack, 0)? {
let span = cmd.span();
let cmd = cmd.coerce_into_string()?;
let mut repl = engine_state.repl_state.lock().expect("repl state mutex");
if call.has_flag(engine_state, stack, "cursor")? {
let cmd_str = cmd.as_string()?;
match cmd_str.parse::<i64>() {
match cmd.parse::<i64>() {
Ok(n) => {
repl.cursor_pos = if n <= 0 {
0usize
@ -90,22 +91,19 @@ impl Command for Commandline {
return Err(ShellError::CantConvert {
to_type: "int".to_string(),
from_type: "string".to_string(),
span: cmd.span(),
help: Some(format!(
r#"string "{cmd_str}" does not represent a valid int"#
)),
span,
help: Some(format!(r#"string "{cmd}" does not represent a valid int"#)),
})
}
}
} else if call.has_flag(engine_state, stack, "append")? {
repl.buffer.push_str(&cmd.as_string()?);
repl.buffer.push_str(&cmd);
} else if call.has_flag(engine_state, stack, "insert")? {
let cmd_str = cmd.as_string()?;
let cursor_pos = repl.cursor_pos;
repl.buffer.insert_str(cursor_pos, &cmd_str);
repl.cursor_pos += cmd_str.len();
repl.buffer.insert_str(cursor_pos, &cmd);
repl.cursor_pos += cmd.len();
} else {
repl.buffer = cmd.as_string()?;
repl.buffer = cmd;
repl.cursor_pos = repl.buffer.len();
}
Ok(Value::nothing(call.head).into_pipeline_data())

View File

@ -112,7 +112,7 @@ pub fn print_events(engine_state: &EngineState) -> Result<Value, ShellError> {
let o = match v {
Value::Record { val, .. } => val
.iter()
.map(|(x, y)| format!("{}: {}", x, y.into_string("", config)))
.map(|(x, y)| format!("{}: {}", x, y.to_expanded_string("", config)))
.collect::<Vec<String>>()
.join(", "),

View File

@ -43,7 +43,7 @@ impl CommandCompletion {
if let Some(paths) = paths {
if let Ok(paths) = paths.as_list() {
for path in paths {
let path = path.as_string().unwrap_or_default();
let path = path.coerce_string().unwrap_or_default();
if let Ok(mut contents) = std::fs::read_dir(path) {
while let Some(Ok(item)) = contents.next() {

View File

@ -474,7 +474,7 @@ pub fn map_value_completions<'a>(
) -> Vec<Suggestion> {
list.filter_map(move |x| {
// Match for string values
if let Ok(s) = x.as_string() {
if let Ok(s) = x.coerce_string() {
return Some(Suggestion {
value: s,
description: None,
@ -507,7 +507,7 @@ pub fn map_value_completions<'a>(
// Match `value` column
if it.0 == "value" {
// Convert the value to string
if let Ok(val_str) = it.1.as_string() {
if let Ok(val_str) = it.1.coerce_string() {
// Update the suggestion value
suggestion.value = val_str;
}
@ -516,7 +516,7 @@ pub fn map_value_completions<'a>(
// Match `description` column
if it.0 == "description" {
// Convert the value to string
if let Ok(desc_str) = it.1.as_string() {
if let Ok(desc_str) = it.1.coerce_string() {
// Update the suggestion value
suggestion.description = Some(desc_str);
}

View File

@ -117,7 +117,7 @@ impl Completer for CustomCompletion {
},
match_algorithm: match options.get("completion_algorithm") {
Some(option) => option
.as_string()
.coerce_string()
.ok()
.and_then(|option| option.try_into().ok())
.unwrap_or(MatchAlgorithm::Prefix),

View File

@ -54,7 +54,7 @@ impl Completer for DotNuCompletion {
.into_iter()
.flat_map(|it| {
it.iter().map(|x| {
x.as_path()
x.to_path()
.expect("internal error: failed to convert lib path")
})
})

View File

@ -28,7 +28,7 @@ pub fn evaluate_commands(
let (block, delta) = {
if let Some(ref t_mode) = table_mode {
let mut config = engine_state.get_config().clone();
config.table_mode = t_mode.as_string()?.parse().unwrap_or_default();
config.table_mode = t_mode.coerce_string()?.parse().unwrap_or_default();
engine_state.set_config(config);
}
@ -59,7 +59,7 @@ pub fn evaluate_commands(
Ok(pipeline_data) => {
let mut config = engine_state.get_config().clone();
if let Some(t_mode) = table_mode {
config.table_mode = t_mode.as_string()?.parse().unwrap_or_default();
config.table_mode = t_mode.coerce_string()?.parse().unwrap_or_default();
}
crate::eval_file::print_table_or_error(engine_state, stack, pipeline_data, &mut config)
}

View File

@ -256,7 +256,7 @@ fn print_or_exit(pipeline_data: PipelineData, engine_state: &mut EngineState, co
std::process::exit(1);
}
let out = item.into_string("\n", config) + "\n";
let out = item.to_expanded_string("\n", config) + "\n";
let _ = stdout_write_all_and_flush(out).map_err(|err| eprintln!("{err}"));
}
}

View File

@ -57,7 +57,7 @@ impl NuHelpCompleter {
if !sig.named.is_empty() {
long_desc.push_str(&get_flags_section(Some(&*self.0.clone()), sig, |v| {
v.into_string_parsable(", ", &self.0.config)
v.to_parsable_string(", ", &self.0.config)
}))
}
@ -73,7 +73,7 @@ impl NuHelpCompleter {
let opt_suffix = if let Some(value) = &positional.default_value {
format!(
" (optional, default: {})",
&value.into_string_parsable(", ", &self.0.config),
&value.to_parsable_string(", ", &self.0.config),
)
} else {
(" (optional)").to_string()

View File

@ -83,10 +83,12 @@ fn convert_to_suggestions(
Value::Record { val, .. } => {
let text = val
.get("value")
.and_then(|val| val.as_string().ok())
.and_then(|val| val.coerce_string().ok())
.unwrap_or_else(|| "No value key".to_string());
let description = val.get("description").and_then(|val| val.as_string().ok());
let description = val
.get("description")
.and_then(|val| val.coerce_string().ok());
let span = match val.get("span") {
Some(Value::Record { val: span, .. }) => {

View File

@ -45,10 +45,9 @@ impl Command for NuHighlight {
};
input.map(
move |x| match x.as_string() {
move |x| match x.coerce_into_string() {
Ok(line) => {
let highlights = highlighter.highlight(&line, line.len());
Value::string(highlights.render_simple(), head)
}
Err(err) => Value::error(err, head),

View File

@ -94,7 +94,7 @@ pub(crate) fn add_menus(
if !config
.menus
.iter()
.any(|menu| menu.name.into_string("", config) == name)
.any(|menu| menu.name.to_expanded_string("", config) == name)
{
let (block, _) = {
let mut working_set = StateWorkingSet::new(&engine_state);
@ -133,7 +133,7 @@ fn add_menu(
) -> Result<Reedline, ShellError> {
let span = menu.menu_type.span();
if let Value::Record { val, .. } = &menu.menu_type {
let layout = extract_value("layout", val, span)?.into_string("", config);
let layout = extract_value("layout", val, span)?.to_expanded_string("", config);
match layout.as_str() {
"columnar" => add_columnar_menu(line_editor, menu, engine_state, stack, config),
@ -142,14 +142,14 @@ fn add_menu(
"description" => add_description_menu(line_editor, menu, engine_state, stack, config),
_ => Err(ShellError::UnsupportedConfigValue {
expected: "columnar, list, ide or description".to_string(),
value: menu.menu_type.into_abbreviated_string(config),
value: menu.menu_type.to_abbreviated_string(config),
span: menu.menu_type.span(),
}),
}
} else {
Err(ShellError::UnsupportedConfigValue {
expected: "only record type".to_string(),
value: menu.menu_type.into_abbreviated_string(config),
value: menu.menu_type.to_abbreviated_string(config),
span: menu.menu_type.span(),
})
}
@ -181,7 +181,7 @@ pub(crate) fn add_columnar_menu(
config: &Config,
) -> Result<Reedline, ShellError> {
let span = menu.menu_type.span();
let name = menu.name.into_string("", config);
let name = menu.name.to_expanded_string("", config);
let mut columnar_menu = ColumnarMenu::default().with_name(&name);
if let Value::Record { val, .. } = &menu.menu_type {
@ -254,7 +254,7 @@ pub(crate) fn add_columnar_menu(
);
}
let marker = menu.marker.into_string("", config);
let marker = menu.marker.to_expanded_string("", config);
columnar_menu = columnar_menu.with_marker(&marker);
let only_buffer_difference = menu.only_buffer_difference.as_bool()?;
@ -280,7 +280,7 @@ pub(crate) fn add_columnar_menu(
}
_ => Err(ShellError::UnsupportedConfigValue {
expected: "block or omitted value".to_string(),
value: menu.source.into_abbreviated_string(config),
value: menu.source.to_abbreviated_string(config),
span,
}),
}
@ -294,7 +294,7 @@ pub(crate) fn add_list_menu(
stack: &Stack,
config: &Config,
) -> Result<Reedline, ShellError> {
let name = menu.name.into_string("", config);
let name = menu.name.to_expanded_string("", config);
let mut list_menu = ListMenu::default().with_name(&name);
let span = menu.menu_type.span();
@ -336,7 +336,7 @@ pub(crate) fn add_list_menu(
);
}
let marker = menu.marker.into_string("", config);
let marker = menu.marker.to_expanded_string("", config);
list_menu = list_menu.with_marker(&marker);
let only_buffer_difference = menu.only_buffer_difference.as_bool()?;
@ -362,7 +362,7 @@ pub(crate) fn add_list_menu(
}
_ => Err(ShellError::UnsupportedConfigValue {
expected: "block or omitted value".to_string(),
value: menu.source.into_abbreviated_string(config),
value: menu.source.to_abbreviated_string(config),
span: menu.source.span(),
}),
}
@ -377,7 +377,7 @@ pub(crate) fn add_ide_menu(
config: &Config,
) -> Result<Reedline, ShellError> {
let span = menu.menu_type.span();
let name = menu.name.into_string("", config);
let name = menu.name.to_expanded_string("", config);
let mut ide_menu = IdeMenu::default().with_name(&name);
if let Value::Record { val, .. } = &menu.menu_type {
@ -442,7 +442,7 @@ pub(crate) fn add_ide_menu(
} else {
return Err(ShellError::UnsupportedConfigValue {
expected: "bool or record".to_string(),
value: border.into_abbreviated_string(config),
value: border.to_abbreviated_string(config),
span: border.span(),
});
}
@ -460,7 +460,7 @@ pub(crate) fn add_ide_menu(
ide_menu = match extract_value("description_mode", val, span) {
Ok(description_mode) => {
let description_mode_str = description_mode.as_string()?;
let description_mode_str = description_mode.coerce_string()?;
match description_mode_str.as_str() {
"left" => ide_menu.with_description_mode(DescriptionMode::Left),
"right" => ide_menu.with_description_mode(DescriptionMode::Right),
@ -468,7 +468,7 @@ pub(crate) fn add_ide_menu(
_ => {
return Err(ShellError::UnsupportedConfigValue {
expected: "\"left\", \"right\" or \"prefer_right\"".to_string(),
value: description_mode.into_abbreviated_string(config),
value: description_mode.to_abbreviated_string(config),
span: description_mode.span(),
});
}
@ -562,7 +562,7 @@ pub(crate) fn add_ide_menu(
);
}
let marker = menu.marker.into_string("", config);
let marker = menu.marker.to_expanded_string("", config);
ide_menu = ide_menu.with_marker(&marker);
let only_buffer_difference = menu.only_buffer_difference.as_bool()?;
@ -588,7 +588,7 @@ pub(crate) fn add_ide_menu(
}
_ => Err(ShellError::UnsupportedConfigValue {
expected: "block or omitted value".to_string(),
value: menu.source.into_abbreviated_string(config),
value: menu.source.to_abbreviated_string(config),
span,
}),
}
@ -602,7 +602,7 @@ pub(crate) fn add_description_menu(
stack: &Stack,
config: &Config,
) -> Result<Reedline, ShellError> {
let name = menu.name.into_string("", config);
let name = menu.name.to_expanded_string("", config);
let mut description_menu = DescriptionMenu::default().with_name(&name);
let span = menu.menu_type.span();
@ -676,7 +676,7 @@ pub(crate) fn add_description_menu(
);
}
let marker = menu.marker.into_string("", config);
let marker = menu.marker.to_expanded_string("", config);
description_menu = description_menu.with_marker(&marker);
let only_buffer_difference = menu.only_buffer_difference.as_bool()?;
@ -706,7 +706,7 @@ pub(crate) fn add_description_menu(
}
_ => Err(ShellError::UnsupportedConfigValue {
expected: "closure or omitted value".to_string(),
value: menu.source.into_abbreviated_string(config),
value: menu.source.to_abbreviated_string(config),
span: menu.source.span(),
}),
}
@ -845,7 +845,7 @@ fn add_keybinding(
}
v => Err(ShellError::UnsupportedConfigValue {
expected: "string or list of strings".to_string(),
value: v.into_abbreviated_string(config),
value: v.to_abbreviated_string(config),
span: v.span(),
}),
}
@ -858,7 +858,7 @@ fn add_parsed_keybinding(
) -> Result<(), ShellError> {
let modifier = match keybinding
.modifier
.into_string("", config)
.to_expanded_string("", config)
.to_ascii_lowercase()
.as_str()
{
@ -875,7 +875,7 @@ fn add_parsed_keybinding(
_ => {
return Err(ShellError::UnsupportedConfigValue {
expected: "CONTROL, SHIFT, ALT or NONE".to_string(),
value: keybinding.modifier.into_abbreviated_string(config),
value: keybinding.modifier.to_abbreviated_string(config),
span: keybinding.modifier.span(),
})
}
@ -883,7 +883,7 @@ fn add_parsed_keybinding(
let keycode = match keybinding
.keycode
.into_string("", config)
.to_expanded_string("", config)
.to_ascii_lowercase()
.as_str()
{
@ -936,7 +936,7 @@ fn add_parsed_keybinding(
_ => {
return Err(ShellError::UnsupportedConfigValue {
expected: "crossterm KeyCode".to_string(),
value: keybinding.keycode.into_abbreviated_string(config),
value: keybinding.keycode.to_abbreviated_string(config),
span: keybinding.keycode.span(),
})
}
@ -974,7 +974,10 @@ fn parse_event(value: &Value, config: &Config) -> Result<Option<ReedlineEvent>,
match value {
Value::Record { val: record, .. } => match EventType::try_from_record(record, span)? {
EventType::Send(value) => event_from_record(
value.into_string("", config).to_ascii_lowercase().as_str(),
value
.to_expanded_string("", config)
.to_ascii_lowercase()
.as_str(),
record,
config,
span,
@ -982,7 +985,10 @@ fn parse_event(value: &Value, config: &Config) -> Result<Option<ReedlineEvent>,
.map(Some),
EventType::Edit(value) => {
let edit = edit_from_record(
value.into_string("", config).to_ascii_lowercase().as_str(),
value
.to_expanded_string("", config)
.to_ascii_lowercase()
.as_str(),
record,
config,
span,
@ -1010,7 +1016,7 @@ fn parse_event(value: &Value, config: &Config) -> Result<Option<ReedlineEvent>,
}
v => Err(ShellError::UnsupportedConfigValue {
expected: "list of events".to_string(),
value: v.into_abbreviated_string(config),
value: v.to_abbreviated_string(config),
span: v.span(),
}),
},
@ -1036,7 +1042,7 @@ fn parse_event(value: &Value, config: &Config) -> Result<Option<ReedlineEvent>,
Value::Nothing { .. } => Ok(None),
v => Err(ShellError::UnsupportedConfigValue {
expected: "record or list of records, null to unbind key".to_string(),
value: v.into_abbreviated_string(config),
value: v.to_abbreviated_string(config),
span: v.span(),
}),
}
@ -1079,11 +1085,11 @@ fn event_from_record(
"openeditor" => ReedlineEvent::OpenEditor,
"menu" => {
let menu = extract_value("name", record, span)?;
ReedlineEvent::Menu(menu.into_string("", config))
ReedlineEvent::Menu(menu.to_expanded_string("", config))
}
"executehostcommand" => {
let cmd = extract_value("cmd", record, span)?;
ReedlineEvent::ExecuteHostCommand(cmd.into_string("", config))
ReedlineEvent::ExecuteHostCommand(cmd.to_expanded_string("", config))
}
v => {
return Err(ShellError::UnsupportedConfigValue {
@ -1188,7 +1194,7 @@ fn edit_from_record(
}
"insertstring" => {
let value = extract_value("value", record, span)?;
EditCommand::InsertString(value.into_string("", config))
EditCommand::InsertString(value.to_expanded_string("", config))
}
"insertnewline" => EditCommand::InsertNewline,
"backspace" => EditCommand::Backspace,
@ -1289,7 +1295,7 @@ fn edit_from_record(
fn extract_char(value: &Value, config: &Config) -> Result<char, ShellError> {
let span = value.span();
value
.into_string("", config)
.to_expanded_string("", config)
.chars()
.next()
.ok_or_else(|| ShellError::MissingConfigValue {

View File

@ -638,9 +638,7 @@ fn do_auto_cd(
let shells = stack.get_env_var(engine_state, "NUSHELL_SHELLS");
let mut shells = if let Some(v) = shells {
v.as_list()
.map(|x| x.to_vec())
.unwrap_or_else(|_| vec![cwd])
v.into_list().unwrap_or_else(|_| vec![cwd])
} else {
vec![cwd]
};
@ -707,7 +705,7 @@ fn do_run_cmd(
if shell_integration {
if let Some(cwd) = stack.get_env_var(engine_state, "PWD") {
let path = cwd.as_string()?;
let path = cwd.coerce_into_string()?;
// Try to abbreviate string for windows title
let maybe_abbrev_path = if let Some(p) = nu_path::home_dir() {
@ -746,7 +744,7 @@ fn do_shell_integration_finalize_command(
) -> Result<()> {
run_ansi_sequence(&get_command_finished_marker(stack, engine_state))?;
if let Some(cwd) = stack.get_env_var(engine_state, "PWD") {
let path = cwd.as_string()?;
let path = cwd.coerce_into_string()?;
// Supported escape sequences of Microsoft's Visual Studio Code (vscode)
// https://code.visualstudio.com/docs/terminal/shell-integration#_supported-escape-sequences