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

@ -127,15 +127,16 @@ impl Command for Histogram {
let span = call.head;
let data_as_value = input.into_value(span);
let value_span = data_as_value.span();
// `input` is not a list, here we can return an error.
run_histogram(
data_as_value.as_list()?.to_vec(),
data_as_value.into_list()?,
column_name,
frequency_column_name,
calc_method,
span,
// Note that as_list() filters out Value::Error here.
data_as_value.span(),
value_span,
)
}
}

View File

@ -261,11 +261,12 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
// Let's try dtparse first
if matches!(input, Value::String { .. }) && dateformat.is_none() {
if let Ok(input_val) = input.as_spanned_string() {
match parse_date_from_string(&input_val.item, input_val.span) {
Ok(date) => return Value::date(date, input_val.span),
let span = input.span();
if let Ok(input_val) = input.coerce_string() {
match parse_date_from_string(&input_val, span) {
Ok(date) => return Value::date(date, span),
Err(_) => {
if let Ok(date) = from_human_time(&input_val.item) {
if let Ok(date) = from_human_time(&input_val) {
match date {
ParseResult::Date(date) => {
let time = NaiveTime::from_hms_opt(0, 0, 0).expect("valid time");
@ -274,10 +275,10 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
combined,
*Local::now().offset(),
);
return Value::date(dt_fixed, input_val.span);
return Value::date(dt_fixed, span);
}
ParseResult::DateTime(date) => {
return Value::date(date.fixed_offset(), input_val.span)
return Value::date(date.fixed_offset(), span)
}
ParseResult::Time(time) => {
let date = Local::now().date_naive();
@ -286,7 +287,7 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
combined,
*Local::now().offset(),
);
return Value::date(dt_fixed, input_val.span);
return Value::date(dt_fixed, span);
}
}
}
@ -338,7 +339,7 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
}
None => Value::error(
ShellError::DatetimeParseError {
msg: input.debug_value(),
msg: input.to_debug_string(),
span: *span,
},
*span,
@ -351,7 +352,7 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
}
None => Value::error(
ShellError::DatetimeParseError {
msg: input.debug_value(),
msg: input.to_debug_string(),
span: *span,
},
*span,

View File

@ -203,8 +203,10 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
Value::Date { val, .. } => Value::string(val.format("%c").to_string(), span),
Value::String { val, .. } => Value::string(val.to_string(), span),
Value::Filesize { val: _, .. } => Value::string(input.into_string(", ", config), span),
Value::Duration { val: _, .. } => Value::string(input.into_string("", config), span),
Value::Filesize { val: _, .. } => {
Value::string(input.to_expanded_string(", ", config), span)
}
Value::Duration { val: _, .. } => Value::string(input.to_expanded_string("", config), span),
Value::Error { error, .. } => Value::string(into_code(error).unwrap_or_default(), span),
Value::Nothing { .. } => Value::string("".to_string(), span),

View File

@ -8,7 +8,7 @@ use nu_protocol::{
};
use once_cell::sync::Lazy;
use regex::{Regex, RegexBuilder};
use std::{collections::HashSet, iter::FromIterator};
use std::collections::HashSet;
#[derive(Clone)]
pub struct IntoValue;
@ -71,14 +71,12 @@ impl Command for IntoValue {
// the columns to update
let columns: Option<Value> = call.get_flag(&engine_state, stack, "columns")?;
let columns: Option<HashSet<String>> = match columns {
Some(val) => {
let cols = val
.as_list()?
.iter()
.map(|val| val.as_string())
.collect::<Result<Vec<String>, ShellError>>()?;
Some(HashSet::from_iter(cols))
}
Some(val) => Some(
val.into_list()?
.into_iter()
.map(Value::coerce_into_string)
.collect::<Result<HashSet<String>, ShellError>>()?,
),
None => None,
};
@ -144,7 +142,7 @@ impl Iterator for UpdateCellIterator {
// for a particular datatype. If it does, it will convert the cell to that datatype.
fn process_cell(val: Value, display_as_filesizes: bool, span: Span) -> Result<Value, ShellError> {
// step 1: convert value to string
let val_str = val.as_string().unwrap_or_default();
let val_str = val.coerce_string().unwrap_or_default();
// step 2: bounce string up against regexes
if BOOLEAN_RE.is_match(&val_str) {

View File

@ -80,7 +80,7 @@ fn helper(value: Value, head: Span) -> Value {
Value::Date { val, .. } => Value::string(humanize_date(val), head),
_ => Value::error(
ShellError::DatetimeParseError {
msg: value.debug_value(),
msg: value.to_debug_string(),
span: head,
},
head,

View File

@ -123,7 +123,7 @@ fn helper(val: Value, head: Span) -> Value {
Value::Date { val, .. } => parse_date_into_table(val, head),
_ => Value::error(
DatetimeParseError {
msg: val.debug_value(),
msg: val.to_debug_string(),
span: head,
},
head,

View File

@ -122,7 +122,7 @@ fn helper(val: Value, head: Span) -> Value {
Value::Date { val, .. } => parse_date_into_table(val, head),
_ => Value::error(
DatetimeParseError {
msg: val.debug_value(),
msg: val.to_debug_string(),
span: head,
},
head,

View File

@ -123,7 +123,7 @@ fn helper(value: Value, head: Span, timezone: &Spanned<String>) -> Value {
}
_ => Value::error(
ShellError::DatetimeParseError {
msg: value.debug_value(),
msg: value.to_debug_string(),
span: head,
},
head,

View File

@ -44,9 +44,9 @@ impl Command for Debug {
input.map(
move |x| {
if raw {
Value::string(x.debug_value(), head)
Value::string(x.to_debug_string(), head)
} else {
Value::string(x.debug_string(", ", &config), head)
Value::string(x.to_expanded_string(", ", &config), head)
}
},
engine_state.ctrlc.clone(),

View File

@ -129,7 +129,7 @@ impl Command for ViewSource {
}
}
value => {
if let Ok(block_id) = value.as_block() {
if let Ok(block_id) = value.coerce_block() {
let block = engine_state.get_block(block_id);
if let Some(span) = block.span {

View File

@ -69,7 +69,7 @@ impl Command for LoadEnv {
if env_var == "PWD" {
let cwd = current_dir(engine_state, stack)?;
let rhs = rhs.as_string()?;
let rhs = rhs.coerce_into_string()?;
let rhs = nu_path::expand_path_with(rhs, cwd);
stack.add_env_var(
env_var,

View File

@ -115,7 +115,7 @@ fn with_env(
// primitive values([X Y W Z])
for row in table.chunks(2) {
if row.len() == 2 {
env.insert(row[0].as_string()?, row[1].clone());
env.insert(row[0].coerce_string()?, row[1].clone());
}
// TODO: else error?
}

View File

@ -79,7 +79,7 @@ impl Command for Cd {
let oldpwd = stack.get_env_var(engine_state, "OLDPWD");
if let Some(oldpwd) = oldpwd {
let path = oldpwd.as_path()?;
let path = oldpwd.to_path()?;
let path = match nu_path::canonicalize_with(path.clone(), &cwd) {
Ok(p) => p,
Err(_) => {

View File

@ -62,8 +62,11 @@ impl Command for Open {
if paths.is_empty() && call.rest_iter(0).next().is_none() {
// try to use path from pipeline input if there were no positional or spread args
let filename = match input {
PipelineData::Value(val, ..) => val.as_spanned_string()?,
let (filename, span) = match input {
PipelineData::Value(val, ..) => {
let span = val.span();
(val.coerce_into_string()?, span)
}
_ => {
return Err(ShellError::MissingParameter {
param_name: "needs filename".to_string(),
@ -73,8 +76,8 @@ impl Command for Open {
};
paths.push(Spanned {
item: NuPath::UnQuoted(filename.item),
span: filename.span,
item: NuPath::UnQuoted(filename),
span,
});
}

View File

@ -332,7 +332,7 @@ fn value_to_bytes(value: Value) -> Result<Vec<u8>, ShellError> {
Value::List { vals, .. } => {
let val = vals
.into_iter()
.map(|it| it.as_string())
.map(Value::coerce_into_string)
.collect::<Result<Vec<String>, ShellError>>()?
.join("\n")
+ "\n";
@ -341,7 +341,7 @@ fn value_to_bytes(value: Value) -> Result<Vec<u8>, ShellError> {
}
// Propagate errors by explicitly matching them before the final case.
Value::Error { error, .. } => Err(*error),
other => Ok(other.as_string()?.into_bytes()),
other => Ok(other.coerce_into_string()?.into_bytes()),
}
}

View File

@ -271,7 +271,7 @@ where
I: IntoIterator<Item = &'a Value>,
{
values.into_iter().any(|v| {
re.is_match(v.into_string(" ", config).as_str())
re.is_match(v.to_expanded_string(" ", config).as_str())
.unwrap_or(false)
})
}
@ -284,13 +284,13 @@ fn highlight_terms_in_string(
string_style: Style,
highlight_style: Style,
) -> Value {
let val_str = val.into_string("", config);
let val_str = val.to_expanded_string("", config);
if let Some(term) = terms
.iter()
.find(|term| contains_ignore_case(&val_str, &term.into_string("", config)))
.find(|term| contains_ignore_case(&val_str, &term.to_expanded_string("", config)))
{
let term_str = term.into_string("", config);
let term_str = term.to_expanded_string("", config);
let highlighted_str =
highlight_search_string(&val_str, &term_str, &string_style, &highlight_style)
.unwrap_or_else(|_| string_style.paint(&term_str).to_string());
@ -312,7 +312,10 @@ fn highlight_terms_in_record_with_search_columns(
highlight_style: Style,
) -> Value {
let col_select = !search_cols.is_empty();
let term_strs: Vec<_> = terms.iter().map(|v| v.into_string("", config)).collect();
let term_strs: Vec<_> = terms
.iter()
.map(|v| v.to_expanded_string("", config))
.collect();
// TODO: change API to mutate in place
let mut record = record.clone();
@ -321,7 +324,7 @@ fn highlight_terms_in_record_with_search_columns(
if col_select && !search_cols.contains(col) {
continue;
}
let val_str = val.into_string("", config);
let val_str = val.to_expanded_string("", config);
let Some(term_str) = term_strs
.iter()
.find(|term_str| contains_ignore_case(&val_str, term_str))
@ -360,7 +363,7 @@ fn find_with_rest_and_highlight(
let terms = call.rest::<Value>(&engine_state, stack, 0)?;
let lower_terms = terms
.iter()
.map(|v| Value::string(v.into_string("", &config).to_lowercase(), span))
.map(|v| Value::string(v.to_expanded_string("", &config).to_lowercase(), span))
.collect::<Vec<Value>>();
let style_computer = StyleComputer::from_config(&engine_state, stack);
@ -466,11 +469,11 @@ fn find_with_rest_and_highlight(
for line in val.split(split_char) {
for term in lower_terms.iter() {
let term_str = term.into_string("", &filter_config);
let term_str = term.to_expanded_string("", &filter_config);
let lower_val = line.to_lowercase();
if lower_val
.contains(&term.into_string("", &config).to_lowercase())
{
if lower_val.contains(
&term.to_expanded_string("", &config).to_lowercase(),
) {
output.push(Value::string(
highlight_search_string(
line,
@ -513,7 +516,10 @@ fn value_should_be_printed(
columns_to_search: &[String],
invert: bool,
) -> bool {
let lower_value = Value::string(value.into_string("", filter_config).to_lowercase(), span);
let lower_value = Value::string(
value.to_expanded_string("", filter_config).to_lowercase(),
span,
);
let mut match_found = lower_terms.iter().any(|term| match value {
Value::Bool { .. }
@ -577,7 +583,7 @@ fn record_matches_term(
}
let lower_val = if !val.is_error() {
Value::string(
val.into_string("", filter_config).to_lowercase(),
val.to_expanded_string("", filter_config).to_lowercase(),
Span::test_data(),
)
} else {

View File

@ -201,13 +201,7 @@ fn flat_value(columns: &[CellPath], item: Value, all: bool) -> Vec<Value> {
if need_flatten {
let records = vals
.into_iter()
.filter_map(|v| {
if let Value::Record { val, .. } = v {
Some(val)
} else {
None
}
})
.filter_map(|v| v.into_record().ok())
.collect();
inner_table = Some(TableInside::FlattenedRows {

View File

@ -208,7 +208,7 @@ pub fn group_cell_path(
continue; // likely the result of a failed optional access, ignore this value
}
let group_key = group_key.as_string()?;
let group_key = group_key.coerce_string()?;
let group = groups.entry(group_key).or_default();
group.push(value);
}
@ -220,7 +220,7 @@ pub fn group_no_grouper(values: Vec<Value>) -> Result<IndexMap<String, Vec<Value
let mut groups: IndexMap<String, Vec<Value>> = IndexMap::new();
for value in values.into_iter() {
let group_key = value.as_string()?;
let group_key = value.coerce_string()?;
let group = groups.entry(group_key).or_default();
group.push(value);
}
@ -259,7 +259,7 @@ fn group_closure(
let key = match s.next() {
Some(Value::Error { .. }) | None => error_key.into(),
Some(return_value) => return_value.as_string()?,
Some(return_value) => return_value.coerce_into_string()?,
};
if s.next().is_some() {

View File

@ -116,7 +116,7 @@ fn extract_headers(
.values()
.enumerate()
.map(|(idx, value)| {
let col = value.into_string("", config);
let col = value.to_expanded_string("", config);
if col.is_empty() {
format!("column{idx}")
} else {

View File

@ -136,7 +136,7 @@ fn insert(
match input {
PipelineData::Value(mut value, metadata) => {
if replacement.as_block().is_ok() {
if replacement.coerce_block().is_ok() {
match (cell_path.members.first(), &mut value) {
(Some(PathMember::String { .. }), Value::List { vals, .. }) => {
let span = replacement.span();
@ -200,7 +200,7 @@ fn insert(
}
if path.is_empty() {
if replacement.as_block().is_ok() {
if replacement.coerce_block().is_ok() {
let span = replacement.span();
let value = stream.next();
let end_of_stream = value.is_none();
@ -232,7 +232,7 @@ fn insert(
pre_elems.push(replacement);
}
} else if let Some(mut value) = stream.next() {
if replacement.as_block().is_ok() {
if replacement.coerce_block().is_ok() {
insert_single_value_by_closure(
&mut value,
replacement,
@ -258,7 +258,7 @@ fn insert(
.into_iter()
.chain(stream)
.into_pipeline_data_with_metadata(metadata, ctrlc))
} else if replacement.as_block().is_ok() {
} else if replacement.coerce_block().is_ok() {
let engine_state = engine_state.clone();
let replacement_span = replacement.span();
let capture_block = Closure::from_value(replacement)?;

View File

@ -264,7 +264,7 @@ fn join_rows(
} = this_row
{
if let Some(this_valkey) = this_record.get(this_join_key) {
if let Some(other_rows) = other.get(&this_valkey.into_string(sep, config)) {
if let Some(other_rows) = other.get(&this_valkey.to_expanded_string(sep, config)) {
if matches!(include_inner, IncludeInner::Yes) {
for other_record in other_rows {
// `other` table contains rows matching `this` row on the join column
@ -346,7 +346,7 @@ fn lookup_table<'a>(
for row in rows {
if let Value::Record { val: record, .. } = row {
if let Some(val) = record.get(on) {
let valkey = val.into_string(sep, config);
let valkey = val.to_expanded_string(sep, config);
map.entry(valkey).or_default().push(record);
}
};

View File

@ -121,12 +121,12 @@ impl Command for Move {
let before_or_after = match (after, before) {
(Some(v), None) => Spanned {
item: BeforeOrAfter::After(v.as_string()?),
span: v.span(),
item: BeforeOrAfter::After(v.coerce_into_string()?),
},
(None, Some(v)) => Spanned {
item: BeforeOrAfter::Before(v.as_string()?),
span: v.span(),
item: BeforeOrAfter::Before(v.coerce_into_string()?),
},
(Some(_), Some(_)) => {
return Err(ShellError::GenericError {
@ -222,7 +222,7 @@ fn move_record_columns(
// Find indices of columns to be moved
for column in columns.iter() {
let column_str = column.as_string()?;
let column_str = column.coerce_string()?;
if let Some(idx) = record.index_of(&column_str) {
column_idx.push(idx);

View File

@ -193,7 +193,7 @@ fn sort_record(
match &a.1 {
Value::String { val, .. } => val.clone(),
val => {
if let Ok(val) = val.as_string() {
if let Ok(val) = val.coerce_string() {
val
} else {
// Values that can't be turned to strings are disregarded by the sort
@ -209,7 +209,7 @@ fn sort_record(
match &b.1 {
Value::String { val, .. } => val.clone(),
val => {
if let Ok(val) = val.as_string() {
if let Ok(val) = val.coerce_string() {
val
} else {
// Values that can't be turned to strings are disregarded by the sort
@ -275,7 +275,10 @@ pub fn sort(
};
if natural {
match (folded_left.as_string(), folded_right.as_string()) {
match (
folded_left.coerce_into_string(),
folded_right.coerce_into_string(),
) {
(Ok(left), Ok(right)) => compare_str(left, right),
_ => Ordering::Equal,
}
@ -285,7 +288,7 @@ pub fn sort(
.unwrap_or(Ordering::Equal)
}
} else if natural {
match (a.as_string(), b.as_string()) {
match (a.coerce_string(), b.coerce_string()) {
(Ok(left), Ok(right)) => compare_str(left, right),
_ => Ordering::Equal,
}
@ -334,7 +337,10 @@ pub fn process(
_ => right_res,
};
if natural {
match (folded_left.as_string(), folded_right.as_string()) {
match (
folded_left.coerce_into_string(),
folded_right.coerce_into_string(),
) {
(Ok(left), Ok(right)) => compare_str(left, right),
_ => Ordering::Equal,
}

View File

@ -96,7 +96,7 @@ pub fn split_by(
match splitter {
Some(v) => {
let splitter = Some(Spanned {
item: v.as_string()?,
item: v.coerce_into_string()?,
span: name,
});
Ok(split(splitter.as_ref(), input, name)?)
@ -133,7 +133,7 @@ pub fn split(
};
match group_key {
Some(group_key) => Ok(group_key.as_string()?),
Some(group_key) => Ok(group_key.coerce_string()?),
None => Err(ShellError::CantFindColumn {
col_name: column_name.item.to_string(),
span: column_name.span,
@ -145,7 +145,7 @@ pub fn split(
data_split(values, Some(&block), span)
}
Grouper::ByColumn(None) => {
let block = move |_, row: &Value| row.as_string();
let block = move |_, row: &Value| row.coerce_string();
data_split(values, Some(&block), span)
}
@ -164,7 +164,7 @@ fn data_group(
let group_key = if let Some(ref grouper) = grouper {
grouper(idx, &value)
} else {
value.as_string()
value.coerce_string()
};
let group = groups.entry(group_key?).or_default();

View File

@ -193,7 +193,7 @@ pub fn transpose(
if let Some(desc) = descs.first() {
match &i.get_data_by_key(desc) {
Some(x) => {
if let Ok(s) = x.as_string() {
if let Ok(s) = x.coerce_string() {
headers.push(s.to_string());
} else {
return Err(ShellError::GenericError {

View File

@ -118,7 +118,7 @@ fn update(
match input {
PipelineData::Value(mut value, metadata) => {
if replacement.as_block().is_ok() {
if replacement.coerce_block().is_ok() {
match (cell_path.members.first(), &mut value) {
(Some(PathMember::String { .. }), Value::List { vals, .. }) => {
let span = replacement.span();
@ -186,7 +186,7 @@ fn update(
// cannot fail since loop above does at least one iteration or returns an error
let value = pre_elems.last_mut().expect("one element");
if replacement.as_block().is_ok() {
if replacement.coerce_block().is_ok() {
update_single_value_by_closure(
value,
replacement,
@ -205,7 +205,7 @@ fn update(
.into_iter()
.chain(stream)
.into_pipeline_data_with_metadata(metadata, ctrlc))
} else if replacement.as_block().is_ok() {
} else if replacement.coerce_block().is_ok() {
let replacement_span = replacement.span();
let engine_state = engine_state.clone();
let capture_block = Closure::from_value(replacement)?;

View File

@ -148,7 +148,7 @@ fn upsert(
match input {
PipelineData::Value(mut value, metadata) => {
if replacement.as_block().is_ok() {
if replacement.coerce_block().is_ok() {
match (cell_path.members.first(), &mut value) {
(Some(PathMember::String { .. }), Value::List { vals, .. }) => {
let span = replacement.span();
@ -214,7 +214,7 @@ fn upsert(
if path.is_empty() {
let span = replacement.span();
let value = stream.next().unwrap_or(Value::nothing(span));
if replacement.as_block().is_ok() {
if replacement.coerce_block().is_ok() {
let capture_block = Closure::from_value(replacement)?;
let block = engine_state.get_block(capture_block.block_id);
let mut stack = stack.captures_to_stack(capture_block.captures);
@ -239,7 +239,7 @@ fn upsert(
pre_elems.push(replacement);
}
} else if let Some(mut value) = stream.next() {
if replacement.as_block().is_ok() {
if replacement.coerce_block().is_ok() {
upsert_single_value_by_closure(
&mut value,
replacement,
@ -265,7 +265,7 @@ fn upsert(
.into_iter()
.chain(stream)
.into_pipeline_data_with_metadata(metadata, ctrlc))
} else if replacement.as_block().is_ok() {
} else if replacement.coerce_block().is_ok() {
let engine_state = engine_state.clone();
let replacement_span = replacement.span();
let capture_block = Closure::from_value(replacement)?;

View File

@ -293,8 +293,8 @@ mod test {
);
} else {
assert_eq!(
actual.unwrap().into_string("", &config),
tc.expected.unwrap().into_string("", &config)
actual.unwrap().to_expanded_string("", &config),
tc.expected.unwrap().to_expanded_string("", &config)
);
}
}

View File

@ -119,7 +119,7 @@ fn to_string_tagged_value(
| Value::CustomValue { .. }
| Value::Filesize { .. }
| Value::CellPath { .. }
| Value::Float { .. } => Ok(v.clone().into_abbreviated_string(config)),
| Value::Float { .. } => Ok(v.clone().to_abbreviated_string(config)),
Value::Date { val, .. } => Ok(val.to_string()),
Value::Nothing { .. } => Ok(String::new()),
// Propagate existing errors

View File

@ -120,12 +120,12 @@ fn fragment(input: Value, pretty: bool, config: &Config) -> String {
};
out.push_str(&markup);
out.push_str(&data.into_string("|", config));
out.push_str(&data.to_expanded_string("|", config));
}
_ => out = table(input.into_pipeline_data(), pretty, config),
}
} else {
out = input.into_string("|", config)
out = input.to_expanded_string("|", config)
}
out.push('\n');
@ -168,7 +168,7 @@ fn table(input: PipelineData, pretty: bool, config: &Config) -> String {
.get(&headers[i])
.cloned()
.unwrap_or_else(|| Value::nothing(span))
.into_string(", ", config);
.to_expanded_string(", ", config);
let new_column_width = value_string.len();
escaped_row.push(value_string);
@ -180,7 +180,7 @@ fn table(input: PipelineData, pretty: bool, config: &Config) -> String {
}
p => {
let value_string =
v_htmlescape::escape(&p.into_abbreviated_string(config)).to_string();
v_htmlescape::escape(&p.to_abbreviated_string(config)).to_string();
escaped_row.push(value_string);
}
}
@ -215,7 +215,7 @@ pub fn group_by(values: PipelineData, head: Span, config: &Config) -> (PipelineD
.or_insert_with(|| vec![val.clone()]);
} else {
lists
.entry(val.into_string(",", config))
.entry(val.to_expanded_string(",", config))
.and_modify(|v: &mut Vec<Value>| v.push(val.clone()))
.or_insert_with(|| vec![val.clone()]);
}

View File

@ -205,7 +205,7 @@ pub fn run_seq_dates(
}
let in_format = match input_format {
Some(i) => match i.as_string() {
Some(i) => match i.coerce_into_string() {
Ok(v) => v,
Err(e) => {
return Err(ShellError::GenericError {
@ -221,7 +221,7 @@ pub fn run_seq_dates(
};
let out_format = match output_format {
Some(i) => match i.as_string() {
Some(o) => match o.coerce_into_string() {
Ok(v) => v,
Err(e) => {
return Err(ShellError::GenericError {

View File

@ -423,7 +423,7 @@ fn display(help: &str, engine_state: &EngineState, stack: &mut Stack, span: Span
Value::string(item, Span::unknown()).into_pipeline_data(),
) {
let result = output.into_value(Span::unknown());
match result.as_string() {
match result.coerce_into_string() {
Ok(s) => {
build.push_str(&s);
}

View File

@ -73,7 +73,7 @@ pub fn http_parse_url(
span: Span,
raw_url: Value,
) -> Result<(String, Url), ShellError> {
let requested_url = raw_url.as_string()?;
let requested_url = raw_url.coerce_into_string()?;
let url = match url::Url::parse(&requested_url) {
Ok(u) => u,
Err(_e) => {
@ -222,8 +222,7 @@ pub fn send_request(
let mut data: Vec<(String, String)> = Vec::with_capacity(val.len());
for (col, val) in val {
let val_string = val.as_string()?;
data.push((col, val_string))
data.push((col, val.coerce_into_string()?))
}
let request_fn = move || {
@ -245,7 +244,7 @@ pub fn send_request(
let data = vals
.chunks(2)
.map(|it| Ok((it[0].as_string()?, it[1].as_string()?)))
.map(|it| Ok((it[0].coerce_string()?, it[1].coerce_string()?)))
.collect::<Result<Vec<(String, String)>, ShellErrorOrRequestError>>()?;
let request_fn = move || {
@ -364,7 +363,7 @@ pub fn request_add_custom_headers(
// primitive values ([key1 val1 key2 val2])
for row in table.chunks(2) {
if row.len() == 2 {
custom_headers.insert(row[0].as_string()?, row[1].clone());
custom_headers.insert(row[0].coerce_string()?, row[1].clone());
}
}
}
@ -380,9 +379,9 @@ pub fn request_add_custom_headers(
}
};
for (k, v) in &custom_headers {
if let Ok(s) = v.as_string() {
request = request.set(k, &s);
for (k, v) in custom_headers {
if let Ok(s) = v.coerce_into_string() {
request = request.set(&k, &s);
}
}
}
@ -684,17 +683,11 @@ pub fn request_handle_response_headers(
}
fn retrieve_http_proxy_from_env(engine_state: &EngineState, stack: &mut Stack) -> Option<String> {
let proxy_value: Option<Value> = stack
stack
.get_env_var(engine_state, "http_proxy")
.or(stack.get_env_var(engine_state, "HTTP_PROXY"))
.or(stack.get_env_var(engine_state, "https_proxy"))
.or(stack.get_env_var(engine_state, "HTTPS_PROXY"))
.or(stack.get_env_var(engine_state, "ALL_PROXY"));
match proxy_value {
Some(value) => match value.as_string() {
Ok(proxy) => Some(proxy),
_ => None,
},
_ => None,
}
.or(stack.get_env_var(engine_state, "ALL_PROXY"))
.and_then(|proxy| proxy.coerce_into_string().ok())
}

View File

@ -70,7 +70,7 @@ fn to_url(input: PipelineData, head: Span) -> Result<PipelineData, ShellError> {
Value::Record { ref val, .. } => {
let mut row_vec = vec![];
for (k, v) in val {
match v.as_string() {
match v.coerce_string() {
Ok(s) => {
row_vec.push((k.clone(), s.to_string()));
}

View File

@ -181,10 +181,10 @@ impl UrlComponents {
if key == "params" {
return match value {
Value::Record { ref val, .. } => {
Value::Record { val, .. } => {
let mut qs = val
.iter()
.map(|(k, v)| match v.as_string() {
.into_iter()
.map(|(k, v)| match v.coerce_into_string() {
Ok(val) => Ok(format!("{k}={val}")),
Err(err) => Err(err),
})
@ -202,7 +202,7 @@ impl UrlComponents {
// if query is present it means that also query_span is set.
return Err(ShellError::IncompatibleParameters {
left_message: format!("Mismatch, qs from params is: {qs}"),
left_span: value.span(),
left_span: value_span,
right_message: format!("instead query is: {q}"),
right_span: self.query_span.unwrap_or(Span::unknown()),
});
@ -224,7 +224,7 @@ impl UrlComponents {
}
// apart from port and params all other keys are strings.
let s = value.as_string()?; // If value fails String conversion, just output this ShellError
let s = value.coerce_into_string()?; // If value fails String conversion, just output this ShellError
if !Self::check_empty_string_ok(&key, &s, value_span)? {
return Ok(self);
}
@ -259,7 +259,7 @@ impl UrlComponents {
// if query is present it means that also params_span is set.
return Err(ShellError::IncompatibleParameters {
left_message: format!("Mismatch, query param is: {s}"),
left_span: value.span(),
left_span: value_span,
right_message: format!("instead qs from params is: {q}"),
right_span: self.params_span.unwrap_or(Span::unknown()),
});
@ -268,7 +268,7 @@ impl UrlComponents {
Ok(Self {
query: Some(format!("?{s}")),
query_span: Some(value.span()),
query_span: Some(value_span),
..self
})
}

View File

@ -75,7 +75,7 @@ impl Command for SubCommand {
}
fn get_url_string(value: &Value, engine_state: &EngineState) -> String {
value.into_string("", engine_state.get_config())
value.to_expanded_string("", engine_state.get_config())
}
fn parse(value: Value, head: Span, engine_state: &EngineState) -> Result<PipelineData, ShellError> {

View File

@ -213,7 +213,7 @@ fn join_single(path: &Path, head: Span, args: &Arguments) -> Value {
}
fn join_list(parts: &[Value], head: Span, span: Span, args: &Arguments) -> Value {
let path: Result<PathBuf, ShellError> = parts.iter().map(Value::as_string).collect();
let path: Result<PathBuf, ShellError> = parts.iter().map(Value::coerce_string).collect();
match path {
Ok(ref path) => join_single(path, head, args),
@ -262,14 +262,14 @@ fn merge_record(record: &Record, head: Span, span: Span) -> Result<PathBuf, Shel
#[cfg(windows)]
if let Some(val) = record.get("prefix") {
let p = val.as_string()?;
let p = val.coerce_string()?;
if !p.is_empty() {
result.push(p);
}
}
if let Some(val) = record.get("parent") {
let p = val.as_string()?;
let p = val.coerce_string()?;
if !p.is_empty() {
result.push(p);
}
@ -277,14 +277,14 @@ fn merge_record(record: &Record, head: Span, span: Span) -> Result<PathBuf, Shel
let mut basename = String::new();
if let Some(val) = record.get("stem") {
let p = val.as_string()?;
let p = val.coerce_string()?;
if !p.is_empty() {
basename.push_str(&p);
}
}
if let Some(val) = record.get("extension") {
let p = val.as_string()?;
let p = val.coerce_string()?;
if !p.is_empty() {
basename.push('.');
basename.push_str(&p);

View File

@ -738,7 +738,7 @@ fn heavy_lifting(code: Value, escape: bool, osc: bool, call: &Call) -> Result<St
});
}
let code_string = if param_is_string {
code.as_string().expect("error getting code as string")
code.coerce_string().expect("error getting code as string")
} else {
"".to_string()
};
@ -798,9 +798,10 @@ fn heavy_lifting(code: Value, escape: bool, osc: bool, call: &Call) -> Result<St
}
}
} else {
let span = code.span();
// This is a record that should look like
// { fg: "#ff0000" bg: "#00ff00" attr: bli }
let record = code.as_record()?;
let record = code.into_record()?;
// create a NuStyle to parse the information into
let mut nu_style = nu_color_config::NuStyle {
fg: None,
@ -810,13 +811,13 @@ fn heavy_lifting(code: Value, escape: bool, osc: bool, call: &Call) -> Result<St
// Iterate and populate NuStyle with real values
for (k, v) in record {
match k.as_str() {
"fg" => nu_style.fg = Some(v.as_string()?),
"bg" => nu_style.bg = Some(v.as_string()?),
"attr" => nu_style.attr = Some(v.as_string()?),
"fg" => nu_style.fg = Some(v.coerce_into_string()?),
"bg" => nu_style.bg = Some(v.coerce_into_string()?),
"attr" => nu_style.attr = Some(v.coerce_into_string()?),
_ => {
return Err(ShellError::IncompatibleParametersSingle {
msg: format!("unknown ANSI format key: expected one of ['fg', 'bg', 'attr'], found '{k}'"),
span: code.span(),
span,
})
}
}

View File

@ -81,7 +81,7 @@ fn action(input: &Value, args: &Arguments, _span: Span) -> Value {
other => {
// Fake stripping ansi for other types and just show the abbreviated string
// instead of showing an error message
Value::string(other.into_abbreviated_string(&args.config), span)
Value::string(other.to_abbreviated_string(&args.config), span)
}
}
}

View File

@ -95,9 +95,9 @@ impl Command for InputList {
let display_value = if let Some(ref cellpath) = display_path {
val.clone()
.follow_cell_path(&cellpath.members, false)?
.into_string(", ", engine_state.get_config())
.to_expanded_string(", ", engine_state.get_config())
} else {
val.into_string(", ", engine_state.get_config())
val.to_expanded_string(", ", engine_state.get_config())
};
Ok(Options {
name: display_value,

View File

@ -81,9 +81,9 @@ fn float(
range_span = spanned_range.span;
if r.is_end_inclusive() {
(r.from.as_float()?, r.to.as_float()?)
} else if r.to.as_float()? >= 1.0 {
(r.from.as_float()?, r.to.as_float()? - 1.0)
(r.from.coerce_float()?, r.to.coerce_float()?)
} else if r.to.coerce_float()? >= 1.0 {
(r.from.coerce_float()?, r.to.coerce_float()? - 1.0)
} else {
(0.0, 0.0)
}

View File

@ -137,7 +137,10 @@ pub fn sort(
};
if natural {
match (folded_left.as_string(), folded_right.as_string()) {
match (
folded_left.coerce_into_string(),
folded_right.coerce_into_string(),
) {
(Ok(left), Ok(right)) => compare_str(left, right),
_ => Ordering::Equal,
}
@ -147,7 +150,7 @@ pub fn sort(
.unwrap_or(Ordering::Equal)
}
} else if natural {
match (a.as_string(), b.as_string()) {
match (a.coerce_string(), b.coerce_string()) {
(Ok(left), Ok(right)) => compare_str(left, right),
_ => Ordering::Equal,
}
@ -196,7 +199,10 @@ pub fn compare(
_ => right_res,
};
if natural {
match (folded_left.as_string(), folded_right.as_string()) {
match (
folded_left.coerce_into_string(),
folded_right.coerce_into_string(),
) {
(Ok(left), Ok(right)) => compare_str(left, right),
_ => Ordering::Equal,
}
@ -206,7 +212,10 @@ pub fn compare(
.unwrap_or(Ordering::Equal)
}
} else if natural {
match (left_res.as_string(), right_res.as_string()) {
match (
left_res.coerce_into_string(),
right_res.coerce_into_string(),
) {
(Ok(left), Ok(right)) => compare_str(left, right),
_ => Ordering::Equal,
}

View File

@ -89,7 +89,7 @@ fn process(
new_table_name
);
for (column_name, column_datatype) in record {
match column_datatype.as_string()?.as_str() {
match column_datatype.coerce_string()?.as_str() {
"int" => {
create_stmt.push_str(&format!("{} INTEGER, ", column_name));
}

View File

@ -227,7 +227,7 @@ fn detect_columns(
.iter()
.take(end_index)
.skip(start_index)
.map(|v| v.as_string().unwrap_or_default())
.map(|v| v.coerce_string().unwrap_or_default())
.join(" ");
let binding = Value::string(combined, Span::unknown());
let last_seg = vals.split_off(end_index);

View File

@ -115,10 +115,10 @@ mod test {
};
let encoded = encode(test_span, encoding.clone(), expected, test_span, true).unwrap();
let encoded = encoded.as_binary().unwrap();
let encoded = encoded.coerce_into_binary().unwrap();
let decoded = decode(test_span, encoding, encoded).unwrap();
let decoded = decoded.as_string().unwrap();
let decoded = decode(test_span, encoding, &encoded).unwrap();
let decoded = decoded.coerce_into_string().unwrap();
assert_eq!(decoded, expected);
}

View File

@ -159,7 +159,7 @@ fn format_helper(value: Value, formatter: &str, formatter_span: Span, head_span:
}
_ => Value::error(
ShellError::DatetimeParseError {
msg: value.debug_value(),
msg: value.to_debug_string(),
span: head_span,
},
head_span,
@ -180,7 +180,7 @@ fn format_helper_rfc2822(value: Value, span: Span) -> Value {
}
_ => Value::error(
ShellError::DatetimeParseError {
msg: value.debug_value(),
msg: value.to_debug_string(),
span,
},
span,

View File

@ -67,7 +67,7 @@ impl Command for FormatDuration {
) -> Result<PipelineData, ShellError> {
let format_value = call
.req::<Value>(engine_state, stack, 0)?
.as_string()?
.coerce_into_string()?
.to_ascii_lowercase();
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);

View File

@ -64,7 +64,7 @@ impl Command for FormatFilesize {
) -> Result<PipelineData, ShellError> {
let format_value = call
.req::<Value>(engine_state, stack, 0)?
.as_string()?
.coerce_into_string()?
.to_ascii_lowercase();
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);

View File

@ -150,7 +150,8 @@ fn operate(
let mut parsed: Vec<Value> = Vec::new();
for v in input {
match v.as_string() {
let v_span = v.span();
match v.coerce_into_string() {
Ok(s) => {
let results = regex_pattern.captures_iter(&s);
@ -168,7 +169,6 @@ fn operate(
}
};
let v_span = v.span();
let record = columns
.iter()
.zip(captures.iter().skip(1))
@ -185,7 +185,7 @@ fn operate(
return Err(ShellError::PipelineMismatch {
exp_input_type: "string".into(),
dst_span: head,
src_span: v.span(),
src_span: v_span,
})
}
}
@ -322,21 +322,22 @@ impl Iterator for ParseStreamer {
}
let v = self.stream.next()?;
let span = v.span();
let Ok(s) = v.as_string() else {
let Ok(s) = v.coerce_into_string() else {
return Some(Value::error(
ShellError::PipelineMismatch {
exp_input_type: "string".into(),
dst_span: self.span,
src_span: v.span(),
src_span: span,
},
v.span(),
span,
));
};
let parsed = stream_helper(
self.regex.clone(),
v.span(),
span,
s,
self.columns.clone(),
&mut self.excess,

View File

@ -124,7 +124,7 @@ fn split_chars_helper(v: &Value, name: Span, graphemes: bool) -> Value {
Value::Error { error, .. } => Value::error(*error.clone(), span),
v => {
let v_span = v.span();
if let Ok(s) = v.as_string() {
if let Ok(s) = v.coerce_string() {
Value::list(
if graphemes {
s.graphemes(true)

View File

@ -148,7 +148,7 @@ fn split_column_helper(
collapse_empty: bool,
head: Span,
) -> Vec<Value> {
if let Ok(s) = v.as_string() {
if let Ok(s) = v.coerce_string() {
let split_result: Vec<_> = separator
.split(&s)
.filter(|x| !(collapse_empty && x.is_empty()))

View File

@ -27,8 +27,8 @@ impl Command for SubCommand {
"The value that denotes what separates the list.",
)
.switch(
"regex",
"separator is a regular expression, matching values that can be coerced into a string",
"regex",
"separator is a regular expression, matching values that can be coerced into a string",
Some('r'))
.category(Category::Filters)
}
@ -160,7 +160,7 @@ enum Matcher {
impl Matcher {
pub fn new(regex: bool, lhs: Value) -> Result<Self, ShellError> {
if regex {
Ok(Matcher::Regex(Regex::new(&lhs.as_string()?).map_err(
Ok(Matcher::Regex(Regex::new(&lhs.coerce_string()?).map_err(
|e| ShellError::GenericError {
error: "Error with regular expression".into(),
msg: e.to_string(),
@ -180,7 +180,7 @@ impl Matcher {
pub fn compare(&self, rhs: &Value) -> Result<bool, ShellError> {
Ok(match self {
Matcher::Regex(regex) => {
if let Ok(rhs_str) = rhs.as_string() {
if let Ok(rhs_str) = rhs.coerce_string() {
regex.is_match(&rhs_str)
} else {
false

View File

@ -152,7 +152,7 @@ fn split_row_helper(v: &Value, regex: &Regex, max_split: Option<usize>, name: Sp
v => {
let v_span = v.span();
if let Ok(s) = v.as_string() {
if let Ok(s) = v.coerce_string() {
match max_split {
Some(max_split) => regex
.splitn(&s, max_split)

View File

@ -158,7 +158,7 @@ fn split_words_helper(v: &Value, word_length: Option<usize>, span: Span, graphem
Value::Error { error, .. } => Value::error(*error.clone(), v_span),
v => {
let v_span = v.span();
if let Ok(s) = v.as_string() {
if let Ok(s) = v.coerce_string() {
// let splits = s.unicode_words();
// let words = trim_to_words(s);
// let words: Vec<&str> = s.split_whitespace().collect();

View File

@ -199,10 +199,10 @@ impl Command for SubCommand {
input.map(
move |v| {
let value_span = v.span();
match v.as_string() {
match v.coerce_into_string() {
Ok(s) => {
let contents = if is_path { s.replace('\\', "\\\\") } else { s };
str_expand(&contents, span, v.span())
str_expand(&contents, span, value_span)
}
Err(_) => Value::error(
ShellError::PipelineMismatch {

View File

@ -49,19 +49,19 @@ impl Command for StrJoin {
let config = engine_state.get_config();
// let output = input.collect_string(&separator.unwrap_or_default(), &config)?;
// Hmm, not sure what we actually want. If you don't use debug_string, Date comes out as human readable
// which feels funny
// Hmm, not sure what we actually want.
// `to_formatted_string` formats dates as human readable which feels funny.
let mut strings: Vec<String> = vec![];
for value in input {
match value {
let str = match value {
Value::Error { error, .. } => {
return Err(*error);
}
value => {
strings.push(value.debug_string("\n", config));
}
}
Value::Date { val, .. } => format!("{val:?}"),
value => value.to_expanded_string("\n", config),
};
strings.push(str);
}
let output = if let Some(separator) = separator {

View File

@ -100,7 +100,7 @@ fn stats(
return Value::error(*error, span);
}
// Now, check if it's a string.
match v.as_string() {
match v.coerce_into_string() {
Ok(s) => counter(&s, span),
Err(_) => Value::error(
ShellError::PipelineMismatch {

View File

@ -296,13 +296,13 @@ fn no_expand_does_not_expand() {
// normally we do expand
let nu_val_expanded = reg_value_to_nu_string(reg_val(), Span::unknown(), false);
assert!(nu_val_expanded.as_string().is_ok());
assert_ne!(nu_val_expanded.as_string().unwrap(), unexpanded);
assert!(nu_val_expanded.coerce_string().is_ok());
assert_ne!(nu_val_expanded.coerce_string().unwrap(), unexpanded);
// unless we skip expansion
let nu_val_skip_expand = reg_value_to_nu_string(reg_val(), Span::unknown(), true);
assert!(nu_val_skip_expand.as_string().is_ok());
assert_eq!(nu_val_skip_expand.as_string().unwrap(), unexpanded);
assert!(nu_val_skip_expand.coerce_string().is_ok());
assert_eq!(nu_val_skip_expand.coerce_string().unwrap(), unexpanded);
}
fn reg_value_to_nu_list_string(reg_value: winreg::RegValue, call_span: Span) -> nu_protocol::Value {

View File

@ -123,7 +123,7 @@ pub fn create_external_command(
let span = value.span();
value
.as_string()
.coerce_string()
.map(|item| Spanned { item, span })
.map_err(|_| ShellError::ExternalCommand {
label: format!("Cannot convert {} to a string", value.get_type()),

View File

@ -114,7 +114,7 @@ prints out the list properly."#
let mut items = vec![];
for (i, (c, v)) in val.into_iter().enumerate() {
items.push((i, c, v.into_string(", ", config)))
items.push((i, c, v.to_expanded_string(", ", config)))
}
Ok(create_grid_output(
@ -265,10 +265,14 @@ fn convert_to_list(
let mut data = vec![];
for (row_num, item) in iter.enumerate() {
if let Value::Error { error, .. } = item {
return Err(*error);
}
let mut row = vec![row_num.to_string()];
if headers.is_empty() {
row.push(item.nonerror_into_string(", ", config)?)
row.push(item.to_expanded_string(", ", config))
} else {
for header in headers.iter().skip(1) {
let result = match &item {
@ -277,7 +281,12 @@ fn convert_to_list(
};
match result {
Some(value) => row.push(value.nonerror_into_string(", ", config)?),
Some(value) => {
if let Value::Error { error, .. } = item {
return Err(*error);
}
row.push(value.to_expanded_string(", ", config));
}
None => row.push(String::new()),
}
}

View File

@ -180,7 +180,9 @@ fn into_sqlite_big_insert() {
span: Span::unknown(),
optional: false,
}],
Box::new(|dateval| Value::string(dateval.as_string().unwrap(), dateval.span())),
Box::new(|dateval| {
Value::string(dateval.coerce_string().unwrap(), dateval.span())
}),
)
.unwrap();