forked from extern/nushell
Range
refactor (#12405)
# Description Currently, `Range` is a struct with a `from`, `to`, and `incr` field, which are all type `Value`. This PR changes `Range` to be an enum over `IntRange` and `FloatRange` for better type safety / stronger compile time guarantees. Fixes: #11778 Fixes: #11777 Fixes: #11776 Fixes: #11775 Fixes: #11774 Fixes: #11773 Fixes: #11769. # User-Facing Changes Hopefully none, besides bug fixes. Although, the `serde` representation might have changed.
This commit is contained in:
@ -125,7 +125,7 @@ fn into_record(
|
||||
),
|
||||
},
|
||||
Value::Range { val, .. } => Value::record(
|
||||
val.into_range_iter(engine_state.ctrlc.clone())?
|
||||
val.into_range_iter(span, engine_state.ctrlc.clone())
|
||||
.enumerate()
|
||||
.map(|(idx, val)| (format!("{idx}"), val))
|
||||
.collect(),
|
||||
|
@ -257,13 +257,7 @@ pub fn debug_string_without_formatting(value: &Value) -> String {
|
||||
Value::Filesize { val, .. } => val.to_string(),
|
||||
Value::Duration { val, .. } => val.to_string(),
|
||||
Value::Date { val, .. } => format!("{val:?}"),
|
||||
Value::Range { val, .. } => {
|
||||
format!(
|
||||
"{}..{}",
|
||||
debug_string_without_formatting(&val.from),
|
||||
debug_string_without_formatting(&val.to)
|
||||
)
|
||||
}
|
||||
Value::Range { val, .. } => val.to_string(),
|
||||
Value::String { val, .. } => val.clone(),
|
||||
Value::Glob { val, .. } => val.clone(),
|
||||
Value::List { vals: val, .. } => format!(
|
||||
|
@ -1,6 +1,7 @@
|
||||
use itertools::Either;
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::{ast::RangeInclusion, PipelineIterator, Range};
|
||||
use nu_protocol::{PipelineIterator, Range};
|
||||
use std::ops::Bound;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DropNth;
|
||||
@ -101,8 +102,8 @@ impl Command for DropNth {
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let metadata = input.metadata();
|
||||
let number_or_range = extract_int_or_range(engine_state, stack, call)?;
|
||||
let mut lower_bound = None;
|
||||
let rows = match number_or_range {
|
||||
|
||||
let rows = match number_or_range.item {
|
||||
Either::Left(row_number) => {
|
||||
let and_rows: Vec<Spanned<i64>> = call.rest(engine_state, stack, 1)?;
|
||||
let mut rows: Vec<_> = and_rows.into_iter().map(|x| x.item as usize).collect();
|
||||
@ -110,66 +111,71 @@ impl Command for DropNth {
|
||||
rows.sort_unstable();
|
||||
rows
|
||||
}
|
||||
Either::Right(row_range) => {
|
||||
let from = row_range.from.as_int()?; // as usize;
|
||||
let to = row_range.to.as_int()?; // as usize;
|
||||
|
||||
Either::Right(Range::FloatRange(_)) => {
|
||||
return Err(ShellError::UnsupportedInput {
|
||||
msg: "float range".into(),
|
||||
input: "value originates from here".into(),
|
||||
msg_span: call.head,
|
||||
input_span: number_or_range.span,
|
||||
});
|
||||
}
|
||||
Either::Right(Range::IntRange(range)) => {
|
||||
// check for negative range inputs, e.g., (2..-5)
|
||||
if from.is_negative() || to.is_negative() {
|
||||
let span: Spanned<Range> = call.req(engine_state, stack, 0)?;
|
||||
return Err(ShellError::TypeMismatch {
|
||||
err_message: "drop nth accepts only positive ints".to_string(),
|
||||
span: span.span,
|
||||
let end_negative = match range.end() {
|
||||
Bound::Included(end) | Bound::Excluded(end) => end < 0,
|
||||
Bound::Unbounded => false,
|
||||
};
|
||||
if range.start().is_negative() || end_negative {
|
||||
return Err(ShellError::UnsupportedInput {
|
||||
msg: "drop nth accepts only positive ints".into(),
|
||||
input: "value originates from here".into(),
|
||||
msg_span: call.head,
|
||||
input_span: number_or_range.span,
|
||||
});
|
||||
}
|
||||
// check if the upper bound is smaller than the lower bound, e.g., do not accept 4..2
|
||||
if to < from {
|
||||
let span: Spanned<Range> = call.req(engine_state, stack, 0)?;
|
||||
return Err(ShellError::TypeMismatch {
|
||||
err_message:
|
||||
"The upper bound needs to be equal or larger to the lower bound"
|
||||
.to_string(),
|
||||
span: span.span,
|
||||
if range.step() < 0 {
|
||||
return Err(ShellError::UnsupportedInput {
|
||||
msg: "The upper bound needs to be equal or larger to the lower bound"
|
||||
.into(),
|
||||
input: "value originates from here".into(),
|
||||
msg_span: call.head,
|
||||
input_span: number_or_range.span,
|
||||
});
|
||||
}
|
||||
|
||||
// check for equality to isize::MAX because for some reason,
|
||||
// the parser returns isize::MAX when we provide a range without upper bound (e.g., 5.. )
|
||||
let mut to = to as usize;
|
||||
let from = from as usize;
|
||||
let start = range.start() as usize;
|
||||
|
||||
if let PipelineData::Value(Value::List { ref vals, .. }, _) = input {
|
||||
let max = from + vals.len() - 1;
|
||||
if to > max {
|
||||
to = max;
|
||||
let end = match range.end() {
|
||||
Bound::Included(end) => end as usize,
|
||||
Bound::Excluded(end) => (end - 1) as usize,
|
||||
Bound::Unbounded => {
|
||||
return Ok(input
|
||||
.into_iter()
|
||||
.take(start)
|
||||
.into_pipeline_data_with_metadata(
|
||||
metadata,
|
||||
engine_state.ctrlc.clone(),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
if to > 0 && to as isize == isize::MAX {
|
||||
lower_bound = Some(from);
|
||||
vec![from]
|
||||
} else if matches!(row_range.inclusion, RangeInclusion::Inclusive) {
|
||||
(from..=to).collect()
|
||||
let end = if let PipelineData::Value(Value::List { vals, .. }, _) = &input {
|
||||
end.min(vals.len() - 1)
|
||||
} else {
|
||||
(from..to).collect()
|
||||
}
|
||||
end
|
||||
};
|
||||
|
||||
(start..=end).collect()
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(lower_bound) = lower_bound {
|
||||
Ok(input
|
||||
.into_iter()
|
||||
.take(lower_bound)
|
||||
.collect::<Vec<_>>()
|
||||
.into_pipeline_data_with_metadata(metadata, engine_state.ctrlc.clone()))
|
||||
} else {
|
||||
Ok(DropNthIterator {
|
||||
input: input.into_iter(),
|
||||
rows,
|
||||
current: 0,
|
||||
}
|
||||
.into_pipeline_data_with_metadata(metadata, engine_state.ctrlc.clone()))
|
||||
Ok(DropNthIterator {
|
||||
input: input.into_iter(),
|
||||
rows,
|
||||
current: 0,
|
||||
}
|
||||
.into_pipeline_data_with_metadata(metadata, engine_state.ctrlc.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
@ -177,11 +183,11 @@ fn extract_int_or_range(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
) -> Result<Either<i64, Range>, ShellError> {
|
||||
let value = call.req::<Value>(engine_state, stack, 0)?;
|
||||
) -> Result<Spanned<Either<i64, Range>>, ShellError> {
|
||||
let value: Value = call.req(engine_state, stack, 0)?;
|
||||
|
||||
let int_opt = value.as_int().map(Either::Left).ok();
|
||||
let range_opt = value.as_range().map(|r| Either::Right(r.clone())).ok();
|
||||
let range_opt = value.as_range().map(Either::Right).ok();
|
||||
|
||||
int_opt
|
||||
.or(range_opt)
|
||||
@ -189,6 +195,10 @@ fn extract_int_or_range(
|
||||
err_message: "int or range".into(),
|
||||
span: value.span(),
|
||||
})
|
||||
.map(|either| Spanned {
|
||||
item: either,
|
||||
span: value.span(),
|
||||
})
|
||||
}
|
||||
|
||||
struct DropNthIterator {
|
||||
|
@ -133,11 +133,15 @@ fn first_helper(
|
||||
}
|
||||
}
|
||||
Value::Range { val, .. } => {
|
||||
let mut iter = val.into_range_iter(span, ctrlc.clone());
|
||||
if return_single_element {
|
||||
Ok(val.from.into_pipeline_data())
|
||||
if let Some(v) = iter.next() {
|
||||
Ok(v.into_pipeline_data())
|
||||
} else {
|
||||
Err(ShellError::AccessEmptyContent { span: head })
|
||||
}
|
||||
} else {
|
||||
Ok(val
|
||||
.into_range_iter(ctrlc.clone())?
|
||||
Ok(iter
|
||||
.take(rows_desired)
|
||||
.into_pipeline_data_with_metadata(metadata, ctrlc))
|
||||
}
|
||||
|
@ -139,84 +139,104 @@ impl Command for ParEach {
|
||||
|
||||
match input {
|
||||
PipelineData::Empty => Ok(PipelineData::Empty),
|
||||
PipelineData::Value(Value::Range { val, .. }, ..) => Ok(create_pool(max_threads)?
|
||||
.install(|| {
|
||||
let vec = val
|
||||
.into_range_iter(ctrlc.clone())
|
||||
.expect("unable to create a range iterator")
|
||||
.enumerate()
|
||||
.par_bridge()
|
||||
.map(move |(index, x)| {
|
||||
let block = engine_state.get_block(block_id);
|
||||
PipelineData::Value(value, ..) => {
|
||||
let span = value.span();
|
||||
match value {
|
||||
Value::List { vals, .. } => Ok(create_pool(max_threads)?.install(|| {
|
||||
let vec = vals
|
||||
.par_iter()
|
||||
.enumerate()
|
||||
.map(move |(index, x)| {
|
||||
let block = engine_state.get_block(block_id);
|
||||
|
||||
let mut stack = stack.clone();
|
||||
let mut stack = stack.clone();
|
||||
|
||||
if let Some(var) = block.signature.get_positional(0) {
|
||||
if let Some(var_id) = &var.var_id {
|
||||
stack.add_var(*var_id, x.clone());
|
||||
if let Some(var) = block.signature.get_positional(0) {
|
||||
if let Some(var_id) = &var.var_id {
|
||||
stack.add_var(*var_id, x.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let val_span = x.span();
|
||||
let x_is_error = x.is_error();
|
||||
let val_span = x.span();
|
||||
let x_is_error = x.is_error();
|
||||
|
||||
let val = match eval_block_with_early_return(
|
||||
engine_state,
|
||||
&mut stack,
|
||||
block,
|
||||
x.into_pipeline_data(),
|
||||
) {
|
||||
Ok(v) => v.into_value(span),
|
||||
Err(error) => Value::error(
|
||||
chain_error_with_input(error, x_is_error, val_span),
|
||||
val_span,
|
||||
),
|
||||
};
|
||||
let val = match eval_block_with_early_return(
|
||||
engine_state,
|
||||
&mut stack,
|
||||
block,
|
||||
x.clone().into_pipeline_data(),
|
||||
) {
|
||||
Ok(v) => v.into_value(span),
|
||||
Err(error) => Value::error(
|
||||
chain_error_with_input(error, x_is_error, val_span),
|
||||
val_span,
|
||||
),
|
||||
};
|
||||
|
||||
(index, val)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
(index, val)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
apply_order(vec).into_pipeline_data(ctrlc)
|
||||
})),
|
||||
PipelineData::Value(Value::List { vals: val, .. }, ..) => Ok(create_pool(max_threads)?
|
||||
.install(|| {
|
||||
let vec = val
|
||||
.par_iter()
|
||||
.enumerate()
|
||||
.map(move |(index, x)| {
|
||||
let block = engine_state.get_block(block_id);
|
||||
apply_order(vec).into_pipeline_data(ctrlc)
|
||||
})),
|
||||
Value::Range { val, .. } => Ok(create_pool(max_threads)?.install(|| {
|
||||
let vec = val
|
||||
.into_range_iter(span, ctrlc.clone())
|
||||
.enumerate()
|
||||
.par_bridge()
|
||||
.map(move |(index, x)| {
|
||||
let block = engine_state.get_block(block_id);
|
||||
|
||||
let mut stack = stack.clone();
|
||||
let mut stack = stack.clone();
|
||||
|
||||
if let Some(var) = block.signature.get_positional(0) {
|
||||
if let Some(var_id) = &var.var_id {
|
||||
stack.add_var(*var_id, x.clone());
|
||||
if let Some(var) = block.signature.get_positional(0) {
|
||||
if let Some(var_id) = &var.var_id {
|
||||
stack.add_var(*var_id, x.clone());
|
||||
}
|
||||
}
|
||||
|
||||
let val_span = x.span();
|
||||
let x_is_error = x.is_error();
|
||||
|
||||
let val = match eval_block_with_early_return(
|
||||
engine_state,
|
||||
&mut stack,
|
||||
block,
|
||||
x.into_pipeline_data(),
|
||||
) {
|
||||
Ok(v) => v.into_value(span),
|
||||
Err(error) => Value::error(
|
||||
chain_error_with_input(error, x_is_error, val_span),
|
||||
val_span,
|
||||
),
|
||||
};
|
||||
|
||||
(index, val)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
apply_order(vec).into_pipeline_data(ctrlc)
|
||||
})),
|
||||
// This match allows non-iterables to be accepted,
|
||||
// which is currently considered undesirable (Nov 2022).
|
||||
value => {
|
||||
let block = engine_state.get_block(block_id);
|
||||
|
||||
if let Some(var) = block.signature.get_positional(0) {
|
||||
if let Some(var_id) = &var.var_id {
|
||||
stack.add_var(*var_id, value.clone());
|
||||
}
|
||||
}
|
||||
|
||||
let val_span = x.span();
|
||||
let x_is_error = x.is_error();
|
||||
|
||||
let val = match eval_block_with_early_return(
|
||||
engine_state,
|
||||
&mut stack,
|
||||
block,
|
||||
x.clone().into_pipeline_data(),
|
||||
) {
|
||||
Ok(v) => v.into_value(span),
|
||||
Err(error) => Value::error(
|
||||
chain_error_with_input(error, x_is_error, val_span),
|
||||
val_span,
|
||||
),
|
||||
};
|
||||
|
||||
(index, val)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
apply_order(vec).into_pipeline_data(ctrlc)
|
||||
})),
|
||||
eval_block_with_early_return(
|
||||
engine_state,
|
||||
&mut stack,
|
||||
block,
|
||||
value.into_pipeline_data(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
PipelineData::ListStream(stream, ..) => Ok(create_pool(max_threads)?.install(|| {
|
||||
let vec = stream
|
||||
.enumerate()
|
||||
@ -294,24 +314,6 @@ impl Command for ParEach {
|
||||
|
||||
apply_order(vec).into_pipeline_data(ctrlc)
|
||||
})),
|
||||
// This match allows non-iterables to be accepted,
|
||||
// which is currently considered undesirable (Nov 2022).
|
||||
PipelineData::Value(x, ..) => {
|
||||
let block = engine_state.get_block(block_id);
|
||||
|
||||
if let Some(var) = block.signature.get_positional(0) {
|
||||
if let Some(var_id) = &var.var_id {
|
||||
stack.add_var(*var_id, x.clone());
|
||||
}
|
||||
}
|
||||
|
||||
eval_block_with_early_return(
|
||||
engine_state,
|
||||
&mut stack,
|
||||
block,
|
||||
x.into_pipeline_data(),
|
||||
)
|
||||
}
|
||||
}
|
||||
.and_then(|x| x.filter(|v| !v.is_nothing(), outer_ctrlc))
|
||||
.map(|res| res.set_metadata(metadata))
|
||||
|
@ -1,5 +1,6 @@
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::ast::RangeInclusion;
|
||||
use nu_protocol::Range as NumRange;
|
||||
use std::ops::Bound;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Range;
|
||||
@ -64,59 +65,68 @@ impl Command for Range {
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let metadata = input.metadata();
|
||||
let rows: nu_protocol::Range = call.req(engine_state, stack, 0)?;
|
||||
let rows: Spanned<NumRange> = call.req(engine_state, stack, 0)?;
|
||||
|
||||
let rows_from = get_range_val(rows.from);
|
||||
let rows_to = if rows.inclusion == RangeInclusion::RightExclusive {
|
||||
get_range_val(rows.to) - 1
|
||||
} else {
|
||||
get_range_val(rows.to)
|
||||
};
|
||||
match rows.item {
|
||||
NumRange::IntRange(range) => {
|
||||
let start = range.start();
|
||||
let end = match range.end() {
|
||||
Bound::Included(end) => end,
|
||||
Bound::Excluded(end) => end - 1,
|
||||
Bound::Unbounded => {
|
||||
if range.step() < 0 {
|
||||
i64::MIN
|
||||
} else {
|
||||
i64::MAX
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// only collect the input if we have any negative indices
|
||||
if rows_from < 0 || rows_to < 0 {
|
||||
let v: Vec<_> = input.into_iter().collect();
|
||||
let vlen: i64 = v.len() as i64;
|
||||
// only collect the input if we have any negative indices
|
||||
if start < 0 || end < 0 {
|
||||
let v: Vec<_> = input.into_iter().collect();
|
||||
let vlen: i64 = v.len() as i64;
|
||||
|
||||
let from = if rows_from < 0 {
|
||||
(vlen + rows_from) as usize
|
||||
} else {
|
||||
rows_from as usize
|
||||
};
|
||||
let from = if start < 0 {
|
||||
(vlen + start) as usize
|
||||
} else {
|
||||
start as usize
|
||||
};
|
||||
|
||||
let to = if rows_to < 0 {
|
||||
(vlen + rows_to) as usize
|
||||
} else if rows_to > v.len() as i64 {
|
||||
v.len()
|
||||
} else {
|
||||
rows_to as usize
|
||||
};
|
||||
let to = if end < 0 {
|
||||
(vlen + end) as usize
|
||||
} else if end > v.len() as i64 {
|
||||
v.len()
|
||||
} else {
|
||||
end as usize
|
||||
};
|
||||
|
||||
if from > to {
|
||||
Ok(PipelineData::Value(Value::nothing(call.head), None))
|
||||
} else {
|
||||
let iter = v.into_iter().skip(from).take(to - from + 1);
|
||||
Ok(iter.into_pipeline_data(engine_state.ctrlc.clone()))
|
||||
}
|
||||
} else {
|
||||
let from = rows_from as usize;
|
||||
let to = rows_to as usize;
|
||||
|
||||
if from > to {
|
||||
Ok(PipelineData::Value(Value::nothing(call.head), None))
|
||||
} else {
|
||||
let iter = input.into_iter().skip(from).take(to - from + 1);
|
||||
Ok(iter.into_pipeline_data(engine_state.ctrlc.clone()))
|
||||
if from > to {
|
||||
Ok(PipelineData::Value(Value::nothing(call.head), None))
|
||||
} else {
|
||||
let iter = v.into_iter().skip(from).take(to - from + 1);
|
||||
Ok(iter.into_pipeline_data(engine_state.ctrlc.clone()))
|
||||
}
|
||||
} else {
|
||||
let from = start as usize;
|
||||
let to = end as usize;
|
||||
|
||||
if from > to {
|
||||
Ok(PipelineData::Value(Value::nothing(call.head), None))
|
||||
} else {
|
||||
let iter = input.into_iter().skip(from).take(to - from + 1);
|
||||
Ok(iter.into_pipeline_data(engine_state.ctrlc.clone()))
|
||||
}
|
||||
}
|
||||
.map(|x| x.set_metadata(metadata))
|
||||
}
|
||||
NumRange::FloatRange(_) => Err(ShellError::UnsupportedInput {
|
||||
msg: "float range".into(),
|
||||
input: "value originates from here".into(),
|
||||
msg_span: call.head,
|
||||
input_span: rows.span,
|
||||
}),
|
||||
}
|
||||
.map(|x| x.set_metadata(metadata))
|
||||
}
|
||||
}
|
||||
|
||||
fn get_range_val(rows_val: Value) -> i64 {
|
||||
match rows_val {
|
||||
Value::Int { val: x, .. } => x,
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -60,7 +60,7 @@ impl Command for Take {
|
||||
Ok(PipelineData::Value(Value::binary(slice, span), metadata))
|
||||
}
|
||||
Value::Range { val, .. } => Ok(val
|
||||
.into_range_iter(ctrlc.clone())?
|
||||
.into_range_iter(span, ctrlc.clone())
|
||||
.take(rows_desired)
|
||||
.into_pipeline_data_with_metadata(metadata, ctrlc)),
|
||||
// Propagate errors by explicitly matching them before the final case.
|
||||
|
@ -292,7 +292,7 @@ fn convert_to_value(
|
||||
};
|
||||
|
||||
Ok(Value::range(
|
||||
Range::new(expr.span, from, next, to, &operator)?,
|
||||
Range::new(from, next, to, operator.inclusion, expr.span)?,
|
||||
expr.span,
|
||||
))
|
||||
}
|
||||
|
@ -2,8 +2,9 @@ use core::fmt::Write;
|
||||
use fancy_regex::Regex;
|
||||
use nu_engine::{command_prelude::*, get_columns};
|
||||
use nu_parser::escape_quote_string;
|
||||
use nu_protocol::ast::RangeInclusion;
|
||||
use nu_protocol::Range;
|
||||
use once_cell::sync::Lazy;
|
||||
use std::ops::Bound;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ToNuon;
|
||||
@ -234,16 +235,26 @@ pub fn value_to_string(
|
||||
}
|
||||
}
|
||||
Value::Nothing { .. } => Ok("null".to_string()),
|
||||
Value::Range { val, .. } => Ok(format!(
|
||||
"{}..{}{}",
|
||||
value_to_string(&val.from, span, depth + 1, indent)?,
|
||||
if val.inclusion == RangeInclusion::RightExclusive {
|
||||
"<"
|
||||
} else {
|
||||
""
|
||||
},
|
||||
value_to_string(&val.to, span, depth + 1, indent)?
|
||||
)),
|
||||
Value::Range { val, .. } => match val {
|
||||
Range::IntRange(range) => Ok(range.to_string()),
|
||||
Range::FloatRange(range) => {
|
||||
let start =
|
||||
value_to_string(&Value::float(range.start(), span), span, depth + 1, indent)?;
|
||||
match range.end() {
|
||||
Bound::Included(end) => Ok(format!(
|
||||
"{}..{}",
|
||||
start,
|
||||
value_to_string(&Value::float(end, span), span, depth + 1, indent)?
|
||||
)),
|
||||
Bound::Excluded(end) => Ok(format!(
|
||||
"{}..<{}",
|
||||
start,
|
||||
value_to_string(&Value::float(end, span), span, depth + 1, indent)?
|
||||
)),
|
||||
Bound::Unbounded => Ok(format!("{start}..",)),
|
||||
}
|
||||
}
|
||||
},
|
||||
Value::Record { val, .. } => {
|
||||
let mut collection = vec![];
|
||||
for (col, val) in &**val {
|
||||
|
@ -116,13 +116,7 @@ fn local_into_string(value: Value, separator: &str, config: &Config) -> String {
|
||||
Value::Date { val, .. } => {
|
||||
format!("{} ({})", val.to_rfc2822(), HumanTime::from(val))
|
||||
}
|
||||
Value::Range { val, .. } => {
|
||||
format!(
|
||||
"{}..{}",
|
||||
local_into_string(val.from, ", ", config),
|
||||
local_into_string(val.to, ", ", config)
|
||||
)
|
||||
}
|
||||
Value::Range { val, .. } => val.to_string(),
|
||||
Value::String { val, .. } => val,
|
||||
Value::Glob { val, .. } => val,
|
||||
Value::List { vals: val, .. } => val
|
||||
|
@ -92,7 +92,7 @@ pub fn calculate(
|
||||
}
|
||||
PipelineData::Value(Value::Range { val, .. }, ..) => {
|
||||
let new_vals: Result<Vec<Value>, ShellError> = val
|
||||
.into_range_iter(None)?
|
||||
.into_range_iter(span, None)
|
||||
.map(|val| mf(&[val], span, name))
|
||||
.collect();
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::Range;
|
||||
use nu_protocol::{FloatRange, Range};
|
||||
use rand::prelude::{thread_rng, Rng};
|
||||
use std::cmp::Ordering;
|
||||
use std::ops::Bound;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SubCommand;
|
||||
@ -68,43 +68,39 @@ fn float(
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let mut range_span = call.head;
|
||||
let span = call.head;
|
||||
let range: Option<Spanned<Range>> = call.opt(engine_state, stack, 0)?;
|
||||
|
||||
let (min, max) = if let Some(spanned_range) = range {
|
||||
let r = spanned_range.item;
|
||||
range_span = spanned_range.span;
|
||||
let mut thread_rng = thread_rng();
|
||||
|
||||
if r.is_end_inclusive() {
|
||||
(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)
|
||||
match range {
|
||||
Some(range) => {
|
||||
let range_span = range.span;
|
||||
let range = FloatRange::from(range.item);
|
||||
|
||||
if range.step() < 0.0 {
|
||||
return Err(ShellError::InvalidRange {
|
||||
left_flank: range.start().to_string(),
|
||||
right_flank: match range.end() {
|
||||
Bound::Included(end) | Bound::Excluded(end) => end.to_string(),
|
||||
Bound::Unbounded => "".into(),
|
||||
},
|
||||
span: range_span,
|
||||
});
|
||||
}
|
||||
|
||||
let value = match range.end() {
|
||||
Bound::Included(end) => thread_rng.gen_range(range.start()..=end),
|
||||
Bound::Excluded(end) => thread_rng.gen_range(range.start()..end),
|
||||
Bound::Unbounded => thread_rng.gen_range(range.start()..f64::INFINITY),
|
||||
};
|
||||
|
||||
Ok(PipelineData::Value(Value::float(value, span), None))
|
||||
}
|
||||
} else {
|
||||
(0.0, 1.0)
|
||||
};
|
||||
|
||||
match min.partial_cmp(&max) {
|
||||
Some(Ordering::Greater) => Err(ShellError::InvalidRange {
|
||||
left_flank: min.to_string(),
|
||||
right_flank: max.to_string(),
|
||||
span: range_span,
|
||||
}),
|
||||
Some(Ordering::Equal) => Ok(PipelineData::Value(
|
||||
Value::float(min, Span::new(64, 64)),
|
||||
None => Ok(PipelineData::Value(
|
||||
Value::float(thread_rng.gen_range(0.0..1.0), span),
|
||||
None,
|
||||
)),
|
||||
_ => {
|
||||
let mut thread_rng = thread_rng();
|
||||
let result: f64 = thread_rng.gen_range(min..max);
|
||||
|
||||
Ok(PipelineData::Value(
|
||||
Value::float(result, Span::new(64, 64)),
|
||||
None,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::Range;
|
||||
use rand::prelude::{thread_rng, Rng};
|
||||
use std::cmp::Ordering;
|
||||
use std::ops::Bound;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SubCommand;
|
||||
@ -71,34 +71,44 @@ fn integer(
|
||||
let span = call.head;
|
||||
let range: Option<Spanned<Range>> = call.opt(engine_state, stack, 0)?;
|
||||
|
||||
let mut range_span = call.head;
|
||||
let (min, max) = if let Some(spanned_range) = range {
|
||||
let r = spanned_range.item;
|
||||
range_span = spanned_range.span;
|
||||
if r.is_end_inclusive() {
|
||||
(r.from.as_int()?, r.to.as_int()?)
|
||||
} else if r.to.as_int()? > 0 {
|
||||
(r.from.as_int()?, r.to.as_int()? - 1)
|
||||
} else {
|
||||
(0, 0)
|
||||
}
|
||||
} else {
|
||||
(0, i64::MAX)
|
||||
};
|
||||
let mut thread_rng = thread_rng();
|
||||
|
||||
match min.partial_cmp(&max) {
|
||||
Some(Ordering::Greater) => Err(ShellError::InvalidRange {
|
||||
left_flank: min.to_string(),
|
||||
right_flank: max.to_string(),
|
||||
span: range_span,
|
||||
}),
|
||||
Some(Ordering::Equal) => Ok(PipelineData::Value(Value::int(min, span), None)),
|
||||
_ => {
|
||||
let mut thread_rng = thread_rng();
|
||||
let result: i64 = thread_rng.gen_range(min..=max);
|
||||
match range {
|
||||
Some(range) => {
|
||||
let range_span = range.span;
|
||||
match range.item {
|
||||
Range::IntRange(range) => {
|
||||
if range.step() < 0 {
|
||||
return Err(ShellError::InvalidRange {
|
||||
left_flank: range.start().to_string(),
|
||||
right_flank: match range.end() {
|
||||
Bound::Included(end) | Bound::Excluded(end) => end.to_string(),
|
||||
Bound::Unbounded => "".into(),
|
||||
},
|
||||
span: range_span,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(PipelineData::Value(Value::int(result, span), None))
|
||||
let value = match range.end() {
|
||||
Bound::Included(end) => thread_rng.gen_range(range.start()..=end),
|
||||
Bound::Excluded(end) => thread_rng.gen_range(range.start()..end),
|
||||
Bound::Unbounded => thread_rng.gen_range(range.start()..=i64::MAX),
|
||||
};
|
||||
|
||||
Ok(PipelineData::Value(Value::int(value, span), None))
|
||||
}
|
||||
Range::FloatRange(_) => Err(ShellError::UnsupportedInput {
|
||||
msg: "float range".into(),
|
||||
input: "value originates from here".into(),
|
||||
msg_span: call.head,
|
||||
input_span: range.span,
|
||||
}),
|
||||
}
|
||||
}
|
||||
None => Ok(PipelineData::Value(
|
||||
Value::int(thread_rng.gen_range(0..=i64::MAX), span),
|
||||
None,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -251,7 +251,6 @@ mod tests {
|
||||
|
||||
let options = Arguments {
|
||||
substring: String::from("Lm"),
|
||||
|
||||
range: None,
|
||||
cell_paths: None,
|
||||
end: false,
|
||||
@ -266,12 +265,14 @@ mod tests {
|
||||
#[test]
|
||||
fn returns_index_of_next_substring() {
|
||||
let word = Value::test_string("Cargo.Cargo");
|
||||
let range = Range {
|
||||
from: Value::int(1, Span::test_data()),
|
||||
incr: Value::int(1, Span::test_data()),
|
||||
to: Value::nothing(Span::test_data()),
|
||||
inclusion: RangeInclusion::Inclusive,
|
||||
};
|
||||
let range = Range::new(
|
||||
Value::int(1, Span::test_data()),
|
||||
Value::nothing(Span::test_data()),
|
||||
Value::nothing(Span::test_data()),
|
||||
RangeInclusion::Inclusive,
|
||||
Span::test_data(),
|
||||
)
|
||||
.expect("valid range");
|
||||
|
||||
let spanned_range = Spanned {
|
||||
item: range,
|
||||
@ -294,12 +295,14 @@ mod tests {
|
||||
#[test]
|
||||
fn index_does_not_exist_due_to_end_index() {
|
||||
let word = Value::test_string("Cargo.Banana");
|
||||
let range = Range {
|
||||
from: Value::nothing(Span::test_data()),
|
||||
inclusion: RangeInclusion::Inclusive,
|
||||
incr: Value::int(1, Span::test_data()),
|
||||
to: Value::int(5, Span::test_data()),
|
||||
};
|
||||
let range = Range::new(
|
||||
Value::nothing(Span::test_data()),
|
||||
Value::nothing(Span::test_data()),
|
||||
Value::int(5, Span::test_data()),
|
||||
RangeInclusion::Inclusive,
|
||||
Span::test_data(),
|
||||
)
|
||||
.expect("valid range");
|
||||
|
||||
let spanned_range = Spanned {
|
||||
item: range,
|
||||
@ -322,12 +325,14 @@ mod tests {
|
||||
#[test]
|
||||
fn returns_index_of_nums_in_middle_due_to_index_limit_from_both_ends() {
|
||||
let word = Value::test_string("123123123");
|
||||
let range = Range {
|
||||
from: Value::int(2, Span::test_data()),
|
||||
incr: Value::int(1, Span::test_data()),
|
||||
to: Value::int(6, Span::test_data()),
|
||||
inclusion: RangeInclusion::Inclusive,
|
||||
};
|
||||
let range = Range::new(
|
||||
Value::int(2, Span::test_data()),
|
||||
Value::nothing(Span::test_data()),
|
||||
Value::int(6, Span::test_data()),
|
||||
RangeInclusion::Inclusive,
|
||||
Span::test_data(),
|
||||
)
|
||||
.expect("valid range");
|
||||
|
||||
let spanned_range = Spanned {
|
||||
item: range,
|
||||
@ -350,12 +355,14 @@ mod tests {
|
||||
#[test]
|
||||
fn index_does_not_exists_due_to_strict_bounds() {
|
||||
let word = Value::test_string("123456");
|
||||
let range = Range {
|
||||
from: Value::int(2, Span::test_data()),
|
||||
incr: Value::int(1, Span::test_data()),
|
||||
to: Value::int(5, Span::test_data()),
|
||||
inclusion: RangeInclusion::RightExclusive,
|
||||
};
|
||||
let range = Range::new(
|
||||
Value::int(2, Span::test_data()),
|
||||
Value::nothing(Span::test_data()),
|
||||
Value::int(5, Span::test_data()),
|
||||
RangeInclusion::RightExclusive,
|
||||
Span::test_data(),
|
||||
)
|
||||
.expect("valid range");
|
||||
|
||||
let spanned_range = Spanned {
|
||||
item: range,
|
||||
@ -381,7 +388,6 @@ mod tests {
|
||||
|
||||
let options = Arguments {
|
||||
substring: String::from("ふが"),
|
||||
|
||||
range: None,
|
||||
cell_paths: None,
|
||||
end: false,
|
||||
@ -396,12 +402,14 @@ mod tests {
|
||||
fn index_is_not_a_char_boundary() {
|
||||
let word = Value::string(String::from("💛"), Span::test_data());
|
||||
|
||||
let range = Range {
|
||||
from: Value::int(0, Span::test_data()),
|
||||
incr: Value::int(1, Span::test_data()),
|
||||
to: Value::int(3, Span::test_data()),
|
||||
inclusion: RangeInclusion::Inclusive,
|
||||
};
|
||||
let range = Range::new(
|
||||
Value::int(0, Span::test_data()),
|
||||
Value::int(1, Span::test_data()),
|
||||
Value::int(3, Span::test_data()),
|
||||
RangeInclusion::Inclusive,
|
||||
Span::test_data(),
|
||||
)
|
||||
.expect("valid range");
|
||||
|
||||
let spanned_range = Spanned {
|
||||
item: range,
|
||||
@ -425,12 +433,14 @@ mod tests {
|
||||
fn index_is_out_of_bounds() {
|
||||
let word = Value::string(String::from("hello"), Span::test_data());
|
||||
|
||||
let range = Range {
|
||||
from: Value::int(-1, Span::test_data()),
|
||||
incr: Value::int(1, Span::test_data()),
|
||||
to: Value::int(3, Span::test_data()),
|
||||
inclusion: RangeInclusion::Inclusive,
|
||||
};
|
||||
let range = Range::new(
|
||||
Value::int(-1, Span::test_data()),
|
||||
Value::int(1, Span::test_data()),
|
||||
Value::int(3, Span::test_data()),
|
||||
RangeInclusion::Inclusive,
|
||||
Span::test_data(),
|
||||
)
|
||||
.expect("valid range");
|
||||
|
||||
let spanned_range = Spanned {
|
||||
item: range,
|
||||
|
@ -409,7 +409,7 @@ fn handle_table_command(
|
||||
}
|
||||
PipelineData::Value(Value::Range { val, .. }, metadata) => {
|
||||
let ctrlc = input.engine_state.ctrlc.clone();
|
||||
let stream = ListStream::from_stream(val.into_range_iter(ctrlc.clone())?, ctrlc);
|
||||
let stream = ListStream::from_stream(val.into_range_iter(span, ctrlc.clone()), ctrlc);
|
||||
input.data = PipelineData::Empty;
|
||||
handle_row_stream(input, cfg, stream, metadata)
|
||||
}
|
||||
|
Reference in New Issue
Block a user