From 09a1f5acb93f9e120759626a2baa26420a7532cc Mon Sep 17 00:00:00 2001 From: Jonathan Turner Date: Thu, 8 Apr 2021 20:15:36 +1200 Subject: [PATCH] Begin migration away from arg serialization (#3281) * initial implementation * Move a few commands over to new arg system * Fix char also --- Cargo.lock | 5 + .../nu-command/src/commands/ansi/command.rs | 45 +-- .../nu-command/src/commands/build_string.rs | 8 +- crates/nu-command/src/commands/cal.rs | 14 +- crates/nu-command/src/commands/char_.rs | 23 +- .../nu-command/src/commands/each/command.rs | 22 +- crates/nu-command/src/commands/echo.rs | 10 +- crates/nu-command/src/commands/if_.rs | 21 +- crates/nu-command/src/commands/let_.rs | 12 +- crates/nu-command/src/commands/sleep.rs | 93 ------ .../nu-command/src/commands/str_/collect.rs | 16 +- .../nu-command/src/commands/table/command.rs | 22 +- crates/nu-command/src/commands/uniq.rs | 2 +- crates/nu-engine/Cargo.toml | 5 + crates/nu-engine/src/command_args.rs | 40 ++- crates/nu-engine/src/from_value.rs | 281 ++++++++++++++++++ crates/nu-engine/src/lib.rs | 2 + crates/nu-engine/src/maybe_text_codec.rs | 13 - crates/nu-protocol/src/value.rs | 8 + crates/nu-protocol/src/value/primitive.rs | 24 ++ 20 files changed, 442 insertions(+), 224 deletions(-) create mode 100644 crates/nu-engine/src/from_value.rs diff --git a/Cargo.lock b/Cargo.lock index 9465f31e2..d43c7a403 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3373,7 +3373,9 @@ dependencies = [ "ansi_term 0.12.1", "async-recursion", "async-trait", + "bigdecimal", "bytes 0.5.6", + "chrono", "codespan-reporting", "derive-new", "dirs-next", @@ -3401,6 +3403,9 @@ dependencies = [ "nu-stream", "nu-test-support", "nu-value-ext", + "num-bigint 0.3.2", + "num-format", + "num-traits 0.2.14", "parking_lot 0.11.1", "rayon", "serde 1.0.124", diff --git a/crates/nu-command/src/commands/ansi/command.rs b/crates/nu-command/src/commands/ansi/command.rs index 5d6b52405..99d28975f 100644 --- a/crates/nu-command/src/commands/ansi/command.rs +++ b/crates/nu-command/src/commands/ansi/command.rs @@ -7,13 +7,6 @@ use nu_source::Tagged; pub struct Command; -#[derive(Deserialize)] -struct AnsiArgs { - code: Value, - escape: Option>, - osc: Option>, -} - impl WholeStreamCommand for Command { fn name(&self) -> &str { "ansi" @@ -120,9 +113,14 @@ Format: # } fn run(&self, args: CommandArgs) -> Result { - let (AnsiArgs { code, escape, osc }, _) = args.process()?; + let args = args.evaluate_once()?; + + let code: Option, ShellError>> = args.opt(0); + let escape: Option, ShellError>> = args.get_flag("escape"); + let osc: Option, ShellError>> = args.get_flag("osc"); if let Some(e) = escape { + let e = e?; let esc_vec: Vec = e.item.chars().collect(); if esc_vec[0] == '\\' { return Err(ShellError::labeled_error( @@ -138,6 +136,7 @@ Format: # } if let Some(o) = osc { + let o = o?; let osc_vec: Vec = o.item.chars().collect(); if osc_vec[0] == '\\' { return Err(ShellError::labeled_error( @@ -155,25 +154,33 @@ Format: # ))); } - let code_string = code.as_string()?; - let ansi_code = str_to_ansi(code_string); + if let Some(code) = code { + let code = code?; + let ansi_code = str_to_ansi(&code.item); - if let Some(output) = ansi_code { - Ok(OutputStream::one(ReturnSuccess::value( - UntaggedValue::string(output).into_value(code.tag()), - ))) + if let Some(output) = ansi_code { + Ok(OutputStream::one(ReturnSuccess::value( + UntaggedValue::string(output).into_value(code.tag()), + ))) + } else { + Err(ShellError::labeled_error( + "Unknown ansi code", + "unknown ansi code", + code.tag(), + )) + } } else { Err(ShellError::labeled_error( - "Unknown ansi code", - "unknown ansi code", - code.tag(), + "Expected ansi code", + "expect ansi code", + args.call_info.name_tag.clone(), )) } } } -pub fn str_to_ansi(s: String) -> Option { - match s.as_str() { +pub fn str_to_ansi(s: &str) -> Option { + match s { "g" | "green" => Some(Color::Green.prefix().to_string()), "gb" | "green_bold" => Some(Color::Green.bold().prefix().to_string()), "gu" | "green_underline" => Some(Color::Green.underline().prefix().to_string()), diff --git a/crates/nu-command/src/commands/build_string.rs b/crates/nu-command/src/commands/build_string.rs index f6251bb23..fe5949f3b 100644 --- a/crates/nu-command/src/commands/build_string.rs +++ b/crates/nu-command/src/commands/build_string.rs @@ -5,11 +5,6 @@ use nu_data::value::format_leaf; use nu_engine::WholeStreamCommand; use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value}; -#[derive(Deserialize)] -pub struct BuildStringArgs { - rest: Vec, -} - pub struct BuildString; impl WholeStreamCommand for BuildString { @@ -28,7 +23,8 @@ impl WholeStreamCommand for BuildString { fn run(&self, args: CommandArgs) -> Result { let tag = args.call_info.name_tag.clone(); - let (BuildStringArgs { rest }, _) = args.process()?; + let args = args.evaluate_once()?; + let rest: Vec = args.rest(0)?; let mut output_string = String::new(); diff --git a/crates/nu-command/src/commands/cal.rs b/crates/nu-command/src/commands/cal.rs index a616399f8..60d444191 100644 --- a/crates/nu-command/src/commands/cal.rs +++ b/crates/nu-command/src/commands/cal.rs @@ -75,7 +75,7 @@ pub fn cal(args: CommandArgs) -> Result { let mut selected_year: i32 = current_year; let mut current_day_option: Option = Some(current_day); - let month_range = if let Some(full_year_value) = args.get("full-year") { + let month_range = if let Some(full_year_value) = args.call_info.args.get("full-year") { if let Ok(year_u64) = full_year_value.as_u64() { selected_year = year_u64 as i32; @@ -209,7 +209,7 @@ fn add_month_to_table( let month_helper = match month_helper_result { Ok(month_helper) => month_helper, - Err(()) => match args.get("full-year") { + Err(()) => match args.call_info.args.get("full-year") { Some(full_year_value) => { return Err(get_invalid_year_shell_error(&full_year_value.tag())) } @@ -235,7 +235,7 @@ fn add_month_to_table( let mut week_start_day = days_of_the_week[0].to_string(); - if let Some(week_start_value) = args.get("week-start") { + if let Some(week_start_value) = args.call_info.args.get("week-start") { if let Ok(day) = week_start_value.as_string() { if days_of_the_week.contains(&day.as_str()) { week_start_day = day; @@ -264,10 +264,10 @@ fn add_month_to_table( let mut day_number: u32 = 1; let day_limit: u32 = total_start_offset + month_helper.number_of_days_in_month; - let should_show_year_column = args.has("year"); - let should_show_quarter_column = args.has("quarter"); - let should_show_month_column = args.has("month"); - let should_show_month_names = args.has("month-names"); + let should_show_year_column = args.has_flag("year"); + let should_show_quarter_column = args.has_flag("quarter"); + let should_show_month_column = args.has_flag("month"); + let should_show_month_names = args.has_flag("month-names"); while day_number <= day_limit { let mut indexmap = IndexMap::new(); diff --git a/crates/nu-command/src/commands/char_.rs b/crates/nu-command/src/commands/char_.rs index fc91a9a86..a2175021c 100644 --- a/crates/nu-command/src/commands/char_.rs +++ b/crates/nu-command/src/commands/char_.rs @@ -1,18 +1,11 @@ use crate::prelude::*; -use nu_engine::WholeStreamCommand; +use nu_engine::{FromValue, WholeStreamCommand}; use nu_errors::ShellError; use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value}; use nu_source::Tagged; pub struct Char; -#[derive(Deserialize)] -struct CharArgs { - name: Tagged, - rest: Vec>, - unicode: bool, -} - impl WholeStreamCommand for Char { fn name(&self) -> &str { "char" @@ -65,14 +58,11 @@ impl WholeStreamCommand for Char { } fn run(&self, args: CommandArgs) -> Result { - let ( - CharArgs { - name, - rest, - unicode, - }, - _, - ) = args.process()?; + let args = args.evaluate_once()?; + + let name: Tagged = args.req(0)?; + let rest: Vec = args.rest(1)?; + let unicode = args.has_flag("unicode"); if unicode { if !rest.is_empty() { @@ -86,6 +76,7 @@ impl WholeStreamCommand for Char { } // Get the rest of the bytes for byte_part in rest { + let byte_part: Tagged = FromValue::from_value(&byte_part)?; let decoded_char = string_to_unicode_char(&byte_part, &byte_part.tag); match decoded_char { Ok(ch) => multi_byte.push(ch), diff --git a/crates/nu-command/src/commands/each/command.rs b/crates/nu-command/src/commands/each/command.rs index 41df256ed..52deca168 100644 --- a/crates/nu-command/src/commands/each/command.rs +++ b/crates/nu-command/src/commands/each/command.rs @@ -6,16 +6,9 @@ use nu_errors::ShellError; use nu_protocol::{ hir::CapturedBlock, Signature, SyntaxShape, TaggedDictBuilder, UntaggedValue, Value, }; -use nu_source::Tagged; pub struct Each; -#[derive(Deserialize)] -pub struct EachArgs { - block: CapturedBlock, - numbered: Tagged, -} - impl WholeStreamCommand for Each { fn name(&self) -> &str { "each" @@ -110,12 +103,16 @@ pub(crate) fn make_indexed_item(index: usize, item: Value) -> Value { fn each(raw_args: CommandArgs) -> Result { let context = Arc::new(EvaluationContext::from_args(&raw_args)); + let args = raw_args.evaluate_once()?; - let (each_args, input): (EachArgs, _) = raw_args.process()?; - let block = Arc::new(Box::new(each_args.block)); + let block: CapturedBlock = args.req(0)?; + let numbered: bool = args.has_flag("numbered"); - if each_args.numbered.item { - Ok(input + let block = Arc::new(Box::new(block)); + + if numbered { + Ok(args + .input .enumerate() .map(move |input| { let block = block.clone(); @@ -130,7 +127,8 @@ fn each(raw_args: CommandArgs) -> Result { .flatten() .to_output_stream()) } else { - Ok(input + Ok(args + .input .map(move |input| { let block = block.clone(); let context = context.clone(); diff --git a/crates/nu-command/src/commands/echo.rs b/crates/nu-command/src/commands/echo.rs index 825ad0aef..43d5484d3 100644 --- a/crates/nu-command/src/commands/echo.rs +++ b/crates/nu-command/src/commands/echo.rs @@ -8,11 +8,6 @@ use nu_protocol::{ pub struct Echo; -#[derive(Deserialize, Debug)] -pub struct EchoArgs { - pub rest: Vec, -} - impl WholeStreamCommand for Echo { fn name(&self) -> &str { "echo" @@ -47,9 +42,10 @@ impl WholeStreamCommand for Echo { } fn echo(args: CommandArgs) -> Result { - let (args, _): (EchoArgs, _) = args.process()?; + let args = args.evaluate_once()?; + let rest: Vec = args.rest(0)?; - let stream = args.rest.into_iter().map(|i| match i.as_string() { + let stream = rest.into_iter().map(|i| match i.as_string() { Ok(s) => OutputStream::one(Ok(ReturnSuccess::Value( UntaggedValue::string(s).into_value(i.tag.clone()), ))), diff --git a/crates/nu-command/src/commands/if_.rs b/crates/nu-command/src/commands/if_.rs index 9893f5b4e..ab196b916 100644 --- a/crates/nu-command/src/commands/if_.rs +++ b/crates/nu-command/src/commands/if_.rs @@ -9,13 +9,6 @@ use nu_protocol::{ pub struct If; -#[derive(Deserialize)] -pub struct IfArgs { - condition: CapturedBlock, - then_case: CapturedBlock, - else_case: CapturedBlock, -} - impl WholeStreamCommand for If { fn name(&self) -> &str { "if" @@ -67,14 +60,12 @@ fn if_command(raw_args: CommandArgs) -> Result { let tag = raw_args.call_info.name_tag.clone(); let context = Arc::new(EvaluationContext::from_args(&raw_args)); - let ( - IfArgs { - condition, - then_case, - else_case, - }, - input, - ) = raw_args.process()?; + let args = raw_args.evaluate_once()?; + let condition: CapturedBlock = args.req(0)?; + let then_case: CapturedBlock = args.req(1)?; + let else_case: CapturedBlock = args.req(2)?; + let input = args.input; + let cond = { if condition.block.block.len() != 1 { return Err(ShellError::labeled_error( diff --git a/crates/nu-command/src/commands/let_.rs b/crates/nu-command/src/commands/let_.rs index a1cad0482..1016df090 100644 --- a/crates/nu-command/src/commands/let_.rs +++ b/crates/nu-command/src/commands/let_.rs @@ -7,13 +7,6 @@ use nu_source::Tagged; pub struct Let; -#[derive(Deserialize)] -pub struct LetArgs { - pub name: Tagged, - pub equals: Tagged, - pub rhs: CapturedBlock, -} - impl WholeStreamCommand for Let { fn name(&self) -> &str { "let" @@ -46,8 +39,11 @@ impl WholeStreamCommand for Let { pub fn letcmd(args: CommandArgs) -> Result { let tag = args.call_info.name_tag.clone(); let ctx = EvaluationContext::from_args(&args); + let args = args.evaluate_once()?; - let (LetArgs { name, rhs, .. }, _) = args.process()?; + //let (LetArgs { name, rhs, .. }, _) = args.process()?; + let name: Tagged = args.req(0)?; + let rhs: CapturedBlock = args.req(2)?; let (expr, captured) = { if rhs.block.block.len() != 1 { diff --git a/crates/nu-command/src/commands/sleep.rs b/crates/nu-command/src/commands/sleep.rs index c040d739e..ff6b5ec72 100644 --- a/crates/nu-command/src/commands/sleep.rs +++ b/crates/nu-command/src/commands/sleep.rs @@ -111,99 +111,6 @@ impl Iterator for SleepIterator { } } -// struct SleepHandler { -// shared_state: Arc>, -// } - -// impl SleepHandler { -// /// Create a new `SleepHandler` which will complete after the provided -// /// timeout and check for Ctrl+C periodically. -// pub fn new(duration: Duration, ctrl_c: Arc) -> Self { -// let shared_state = Arc::new(Mutex::new(SharedState { -// done: false, -// waker: None, -// })); - -// // Spawn the main sleep thread -// let thread_shared_state = shared_state.clone(); -// thread::spawn(move || { -// thread::sleep(duration); -// let mut shared_state = thread_shared_state.lock(); -// // Signal that the timer has completed and wake up the last -// // task on which the future was polled, if one exists. -// if !shared_state.done { -// shared_state.done = true; -// if let Some(waker) = shared_state.waker.take() { -// waker.wake() -// } -// } -// }); - -// // Spawn the Ctrl+C-watching polling thread -// let thread_shared_state = shared_state.clone(); -// thread::spawn(move || { -// loop { -// { -// let mut shared_state = thread_shared_state.lock(); -// // exit if the main thread is done -// if shared_state.done { -// return; -// } -// // finish the future prematurely if Ctrl+C has been pressed -// if ctrl_c.load(Ordering::SeqCst) { -// shared_state.done = true; -// if let Some(waker) = shared_state.waker.take() { -// waker.wake() -// } -// return; -// } -// } -// // sleep for a short time -// thread::sleep(CTRL_C_CHECK_INTERVAL); -// } -// }); - -// SleepHandler { shared_state } -// } -// } - -// struct SharedState { -// done: bool, -// } - -// impl Iterator for SleepHandler { -// type Item = (); - -// fn next(&mut self) -> Option { -// let mut shared_state = self.shared_state.lock(); -// loop { -// if shared_state.done { -// return None; -// } -// } -// } -// } -// impl Future for SleepHandler { -// type Output = (); - -// fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll { -// // Look at the shared state to see if the timer has already completed. -// Poll::Ready(()) -// } else { -// // Set the waker if necessary -// if shared_state -// .waker -// .as_ref() -// .map(|waker| !waker.will_wake(&cx.waker())) -// .unwrap_or(true) -// { -// shared_state.waker = Some(cx.waker().clone()); -// } -// Poll::Pending -// } -// } -// } - #[cfg(test)] mod tests { use super::Sleep; diff --git a/crates/nu-command/src/commands/str_/collect.rs b/crates/nu-command/src/commands/str_/collect.rs index e78602cda..f11f71d08 100644 --- a/crates/nu-command/src/commands/str_/collect.rs +++ b/crates/nu-command/src/commands/str_/collect.rs @@ -6,11 +6,6 @@ use nu_source::Tagged; pub struct SubCommand; -#[derive(Deserialize)] -pub struct SubCommandArgs { - separator: Option>, -} - impl WholeStreamCommand for SubCommand { fn name(&self) -> &str { "str collect" @@ -43,8 +38,15 @@ impl WholeStreamCommand for SubCommand { pub fn collect(args: CommandArgs) -> Result { let tag = args.call_info.name_tag.clone(); - let (SubCommandArgs { separator }, input) = args.process()?; - let separator = separator.map(|tagged| tagged.item).unwrap_or_default(); + //let (SubCommandArgs { separator }, input) = args.process()?; + let args = args.evaluate_once()?; + let separator: Option, ShellError>> = args.opt(0); + let input = args.input; + let separator = if let Some(separator) = separator { + separator?.item + } else { + "".into() + }; let strings: Vec> = input.map(|value| value.as_string()).collect(); let strings: Result, _> = strings.into_iter().collect::>(); diff --git a/crates/nu-command/src/commands/table/command.rs b/crates/nu-command/src/commands/table/command.rs index 2a4525814..28d43ee48 100644 --- a/crates/nu-command/src/commands/table/command.rs +++ b/crates/nu-command/src/commands/table/command.rs @@ -4,7 +4,7 @@ use crate::primitive::get_color_config; use nu_data::value::{format_leaf, style_leaf}; use nu_engine::WholeStreamCommand; use nu_errors::ShellError; -use nu_protocol::{Primitive, Signature, SyntaxShape, UntaggedValue, Value}; +use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value}; use nu_table::{draw_table, Alignment, StyledString, TextStyle}; use std::collections::HashMap; use std::sync::atomic::Ordering; @@ -169,22 +169,10 @@ fn table(configuration: TableConfiguration, args: CommandArgs) -> Result { - if let Some(num) = i.to_usize() { - num - } else { - return Err(ShellError::labeled_error( - "Expected a row number", - "expected a row number", - &args.args.call_info.name_tag, - )); - } - } - _ => 0, + let mut start_number = if let Some(f) = args.get_flag("start_number") { + f? + } else { + 0 }; let mut delay_slot = None; diff --git a/crates/nu-command/src/commands/uniq.rs b/crates/nu-command/src/commands/uniq.rs index 732d71a7f..f0fc8b990 100644 --- a/crates/nu-command/src/commands/uniq.rs +++ b/crates/nu-command/src/commands/uniq.rs @@ -55,7 +55,7 @@ impl WholeStreamCommand for Uniq { fn uniq(args: CommandArgs) -> Result { let args = args.evaluate_once()?; - let should_show_count = args.has("count"); + let should_show_count = args.has_flag("count"); let input = args.input; let uniq_values = { let mut counter = IndexMap::::new(); diff --git a/crates/nu-engine/Cargo.toml b/crates/nu-engine/Cargo.toml index be2399587..199fd1750 100644 --- a/crates/nu-engine/Cargo.toml +++ b/crates/nu-engine/Cargo.toml @@ -24,7 +24,9 @@ dyn-clone = "1.0.4" ansi_term = "0.12.1" async-recursion = "0.3.2" async-trait = "0.1.42" +bigdecimal = "0.2.0" bytes = "0.5.6" +chrono = { version = "0.4.19", features = ["serde"] } derive-new = "0.5.8" dirs-next = { version = "2.0.0", optional = true } dunce = "1.0.1" @@ -39,6 +41,9 @@ glob = "0.3.0" indexmap = { version = "1.6.1", features = ["serde-1"] } itertools = "0.10.0" log = "0.4.14" +num-bigint = { version = "0.3.1", features = ["serde"] } +num-format = "0.4.0" +num-traits = "0.2.14" parking_lot = "0.11.1" rayon = "1.5.0" serde = { version = "1.0.123", features = ["derive"] } diff --git a/crates/nu-engine/src/command_args.rs b/crates/nu-engine/src/command_args.rs index ba8f7d077..9101604cb 100644 --- a/crates/nu-engine/src/command_args.rs +++ b/crates/nu-engine/src/command_args.rs @@ -3,6 +3,7 @@ use crate::env::host::Host; use crate::evaluate::scope::Scope; use crate::evaluation_context::EvaluationContext; use crate::shell::shell_manager::ShellManager; +use crate::FromValue; use crate::{call_info::UnevaluatedCallInfo, config_holder::ConfigHolder}; use derive_new::new; use getset::Getters; @@ -171,11 +172,44 @@ impl EvaluatedCommandArgs { .ok_or_else(|| ShellError::unimplemented("Better error: expect_nth")) } - pub fn get(&self, name: &str) -> Option<&Value> { - self.call_info.args.get(name) + pub fn get_flag(&self, name: &str) -> Option> { + self.call_info + .args + .get(name) + .map(|x| FromValue::from_value(x)) } - pub fn has(&self, name: &str) -> bool { + pub fn has_flag(&self, name: &str) -> bool { self.call_info.args.has(name) } + + pub fn req(&self, pos: usize) -> Result { + if let Some(v) = self.nth(pos) { + FromValue::from_value(v) + } else { + Err(ShellError::labeled_error( + "Position beyond end of command arguments", + "can't access beyond end of command arguments", + self.call_info.name_tag.span, + )) + } + } + + pub fn opt(&self, pos: usize) -> Option> { + if let Some(v) = self.nth(pos) { + Some(FromValue::from_value(v)) + } else { + None + } + } + + pub fn rest(&self, starting_pos: usize) -> Result, ShellError> { + let mut output = vec![]; + + for val in self.call_info.args.positional_iter().skip(starting_pos) { + output.push(FromValue::from_value(val)?); + } + + Ok(output) + } } diff --git a/crates/nu-engine/src/from_value.rs b/crates/nu-engine/src/from_value.rs new file mode 100644 index 000000000..a0de716dd --- /dev/null +++ b/crates/nu-engine/src/from_value.rs @@ -0,0 +1,281 @@ +use std::path::PathBuf; + +use chrono::{DateTime, FixedOffset}; +use nu_errors::ShellError; +use nu_protocol::{ + hir::CapturedBlock, ColumnPath, Dictionary, Primitive, Range, UntaggedValue, Value, +}; +use nu_source::{Tagged, TaggedItem}; + +pub trait FromValue: Sized { + fn from_value(v: &Value) -> Result; +} + +impl FromValue for Value { + fn from_value(v: &Value) -> Result { + Ok(v.clone()) + } +} + +impl FromValue for num_bigint::BigInt { + fn from_value(v: &Value) -> Result { + match v { + Value { + value: UntaggedValue::Primitive(Primitive::Int(i)), + .. + } + | Value { + value: UntaggedValue::Primitive(Primitive::Filesize(i)), + .. + } + | Value { + value: UntaggedValue::Primitive(Primitive::Duration(i)), + .. + } => Ok(i.clone()), + Value { tag, .. } => Err(ShellError::labeled_error( + "Can't convert to integer", + "can't convert to integer", + tag.span, + )), + } + } +} + +impl FromValue for u64 { + fn from_value(v: &Value) -> Result { + v.as_u64() + } +} + +impl FromValue for usize { + fn from_value(v: &Value) -> Result { + v.as_usize() + } +} + +impl FromValue for i64 { + fn from_value(v: &Value) -> Result { + v.as_i64() + } +} + +impl FromValue for i32 { + fn from_value(v: &Value) -> Result { + v.as_i32() + } +} + +impl FromValue for bigdecimal::BigDecimal { + fn from_value(v: &Value) -> Result { + match v { + Value { + value: UntaggedValue::Primitive(Primitive::Decimal(d)), + .. + } => Ok(d.clone()), + Value { tag, .. } => Err(ShellError::labeled_error( + "Can't convert to decimal", + "can't convert to decimal", + tag.span, + )), + } + } +} + +impl FromValue for String { + fn from_value(v: &Value) -> Result { + match v { + Value { + value: UntaggedValue::Primitive(Primitive::String(s)), + .. + } => Ok(s.clone()), + Value { + value: UntaggedValue::Primitive(Primitive::GlobPattern(s)), + .. + } => Ok(s.clone()), + Value { + value: UntaggedValue::Primitive(Primitive::FilePath(p)), + .. + } => Ok(p.to_string_lossy().to_string()), + Value { tag, .. } => Err(ShellError::labeled_error( + "Can't convert to string", + "can't convert to string", + tag.span, + )), + } + } +} + +impl FromValue for Tagged { + fn from_value(v: &Value) -> Result { + let tag = v.tag.clone(); + v.as_string().map(|s| s.tagged(tag)) + } +} + +impl FromValue for PathBuf { + fn from_value(v: &Value) -> Result { + match v { + Value { + value: UntaggedValue::Primitive(Primitive::String(s)), + .. + } => Ok(PathBuf::from(s)), + Value { + value: UntaggedValue::Primitive(Primitive::FilePath(p)), + .. + } => Ok(p.clone()), + Value { tag, .. } => Err(ShellError::labeled_error( + "Can't convert to filepath", + "can't convert to filepath", + tag.span, + )), + } + } +} + +impl FromValue for ColumnPath { + fn from_value(v: &Value) -> Result { + match v { + Value { + value: UntaggedValue::Primitive(Primitive::ColumnPath(c)), + .. + } => Ok(c.clone()), + Value { tag, .. } => Err(ShellError::labeled_error( + "Can't convert to column path", + "can't convert to column path", + tag.span, + )), + } + } +} + +impl FromValue for bool { + fn from_value(v: &Value) -> Result { + match v { + Value { + value: UntaggedValue::Primitive(Primitive::Boolean(b)), + .. + } => Ok(*b), + Value { tag, .. } => Err(ShellError::labeled_error( + "Can't convert to boolean", + "can't convert to boolean", + tag.span, + )), + } + } +} + +impl FromValue for Tagged { + fn from_value(v: &Value) -> Result { + match v { + Value { + value: UntaggedValue::Primitive(Primitive::Boolean(b)), + tag, + } => Ok((*b).tagged(tag)), + Value { tag, .. } => Err(ShellError::labeled_error( + "Can't convert to boolean", + "can't convert to boolean", + tag.span, + )), + } + } +} + +impl FromValue for DateTime { + fn from_value(v: &Value) -> Result { + match v { + Value { + value: UntaggedValue::Primitive(Primitive::Date(d)), + .. + } => Ok(*d), + Value { tag, .. } => Err(ShellError::labeled_error( + "Can't convert to date", + "can't convert to date", + tag.span, + )), + } + } +} + +impl FromValue for Range { + fn from_value(v: &Value) -> Result { + match v { + Value { + value: UntaggedValue::Primitive(Primitive::Range(r)), + .. + } => Ok((**r).clone()), + Value { tag, .. } => Err(ShellError::labeled_error( + "Can't convert to range", + "can't convert to range", + tag.span, + )), + } + } +} + +impl FromValue for Vec { + fn from_value(v: &Value) -> Result { + match v { + Value { + value: UntaggedValue::Primitive(Primitive::Binary(b)), + .. + } => Ok(b.clone()), + Value { + value: UntaggedValue::Primitive(Primitive::String(s)), + .. + } => Ok(s.bytes().collect()), + Value { tag, .. } => Err(ShellError::labeled_error( + "Can't convert to binary data", + "can't convert to binary data", + tag.span, + )), + } + } +} + +impl FromValue for Dictionary { + fn from_value(v: &Value) -> Result { + match v { + Value { + value: UntaggedValue::Row(r), + .. + } => Ok(r.clone()), + Value { tag, .. } => Err(ShellError::labeled_error( + "Can't convert to row", + "can't convert to row", + tag.span, + )), + } + } +} + +impl FromValue for CapturedBlock { + fn from_value(v: &Value) -> Result { + match v { + Value { + value: UntaggedValue::Block(b), + .. + } => Ok((**b).clone()), + Value { tag, .. } => Err(ShellError::labeled_error( + "Can't convert to block", + "can't convert to block", + tag.span, + )), + } + } +} + +impl FromValue for Vec { + fn from_value(v: &Value) -> Result { + match v { + Value { + value: UntaggedValue::Table(t), + .. + } => Ok(t.clone()), + Value { tag, .. } => Err(ShellError::labeled_error( + "Can't convert to table", + "can't convert to table", + tag.span, + )), + } + } +} diff --git a/crates/nu-engine/src/lib.rs b/crates/nu-engine/src/lib.rs index ef8cd3335..b66fe1408 100644 --- a/crates/nu-engine/src/lib.rs +++ b/crates/nu-engine/src/lib.rs @@ -10,6 +10,7 @@ mod evaluate; mod evaluation_context; mod example; pub mod filesystem; +mod from_value; mod maybe_text_codec; pub mod plugin; mod print; @@ -36,6 +37,7 @@ pub use crate::example::Example; pub use crate::filesystem::dir_info::{DirBuilder, DirInfo, FileInfo}; pub use crate::filesystem::filesystem_shell::FilesystemShell; pub use crate::filesystem::path; +pub use crate::from_value::FromValue; pub use crate::maybe_text_codec::{BufCodecReader, MaybeTextCodec, StringOrBinary}; pub use crate::print::maybe_print_errors; pub use crate::runnable_context::RunnableContext; diff --git a/crates/nu-engine/src/maybe_text_codec.rs b/crates/nu-engine/src/maybe_text_codec.rs index 0d2c7ed93..781f37c9d 100644 --- a/crates/nu-engine/src/maybe_text_codec.rs +++ b/crates/nu-engine/src/maybe_text_codec.rs @@ -71,19 +71,6 @@ impl Default for MaybeTextCodec { } } -// impl MaybeTextCodec { -// fn encode(&mut self, item: StringOrBinary, mut dst: &mut [u8]) { -// match item { -// StringOrBinary::String(s) => { -// dst.put(s.as_bytes()); -// } -// StringOrBinary::Binary(b) => { -// dst.put(Bytes::from(b)); -// } -// } -// } -// } - impl MaybeTextCodec { pub fn decode(&mut self, src: &[u8]) -> Result, ShellError> { if src.is_empty() { diff --git a/crates/nu-protocol/src/value.rs b/crates/nu-protocol/src/value.rs index 569a1d926..9d8f7da7f 100644 --- a/crates/nu-protocol/src/value.rs +++ b/crates/nu-protocol/src/value.rs @@ -429,6 +429,14 @@ impl Value { matches!(&self.value, UntaggedValue::Primitive(_)) } + /// View the Value as unsigned size, if possible + pub fn as_usize(&self) -> Result { + match &self.value { + UntaggedValue::Primitive(primitive) => primitive.as_usize(self.tag.span), + _ => Err(ShellError::type_error("integer", self.spanned_type_name())), + } + } + /// View the Value as unsigned 64-bit, if possible pub fn as_u64(&self) -> Result { match &self.value { diff --git a/crates/nu-protocol/src/value/primitive.rs b/crates/nu-protocol/src/value/primitive.rs index e91a3e40a..c89107c39 100644 --- a/crates/nu-protocol/src/value/primitive.rs +++ b/crates/nu-protocol/src/value/primitive.rs @@ -60,6 +60,30 @@ pub enum Primitive { } impl Primitive { + /// Converts a primitive value to a u64, if possible. Uses a span to build an error if the conversion isn't possible. + pub fn as_usize(&self, span: Span) -> Result { + match self { + Primitive::Int(int) => int.to_usize().ok_or_else(|| { + ShellError::range_error( + ExpectedRange::U64, + &format!("{}", int).spanned(span), + "converting an integer into an unsigned 64-bit integer", + ) + }), + Primitive::Decimal(decimal) => decimal.to_usize().ok_or_else(|| { + ShellError::range_error( + ExpectedRange::U64, + &format!("{}", decimal).spanned(span), + "converting a decimal into an unsigned 64-bit integer", + ) + }), + other => Err(ShellError::type_error( + "number", + other.type_name().spanned(span), + )), + } + } + /// Converts a primitive value to a u64, if possible. Uses a span to build an error if the conversion isn't possible. pub fn as_u64(&self, span: Span) -> Result { match self {