diff --git a/crates/nu-command/src/debug/debug_.rs b/crates/nu-command/src/debug/debug_.rs index 8ce3407c89..1e559158b3 100644 --- a/crates/nu-command/src/debug/debug_.rs +++ b/crates/nu-command/src/debug/debug_.rs @@ -23,6 +23,11 @@ impl Command for Debug { ]) .category(Category::Debug) .switch("raw", "Prints the raw value representation", Some('r')) + .switch( + "raw-value", + "Prints the raw value representation but not the nushell value part", + Some('v'), + ) } fn run( @@ -35,6 +40,7 @@ impl Command for Debug { let head = call.head; let config = stack.get_config(engine_state); let raw = call.has_flag(engine_state, stack, "raw")?; + let raw_value = call.has_flag(engine_state, stack, "raw-value")?; // Should PipelineData::Empty result in an error here? @@ -42,6 +48,11 @@ impl Command for Debug { move |x| { if raw { Value::string(x.to_debug_string(), head) + } else if raw_value { + match x.coerce_into_string_all() { + Ok(s) => Value::string(format!("{s:#?}"), head), + Err(e) => Value::error(e, head), + } } else { Value::string(x.to_expanded_string(", ", &config), head) } @@ -78,10 +89,75 @@ impl Command for Debug { Span::test_data(), )), }, + Example { + description: "Debug print an ansi escape encoded string and get the raw value", + example: "$'(ansi red)nushell(ansi reset)' | debug -v", + result: Some(Value::test_string("\"\\u{1b}[31mnushell\\u{1b}[0m\"")), + }, ] } } +// This is just a local Value Extension trait to avoid having to +// put another *_to_string() converter in nu_protocol +trait ValueExt { + fn coerce_into_string_all(&self) -> Result; + fn cant_convert_to(&self, typ: &str) -> Result; +} + +impl ValueExt for Value { + fn cant_convert_to(&self, typ: &str) -> Result { + Err(ShellError::CantConvert { + to_type: typ.into(), + from_type: self.get_type().to_string(), + span: self.span(), + help: None, + }) + } + + fn coerce_into_string_all(&self) -> Result { + let span = self.span(); + match self { + Value::Bool { val, .. } => Ok(val.to_string()), + Value::Int { val, .. } => Ok(val.to_string()), + Value::Float { val, .. } => Ok(val.to_string()), + Value::String { val, .. } => Ok(val.to_string()), + Value::Glob { val, .. } => Ok(val.to_string()), + Value::Filesize { val, .. } => Ok(val.get().to_string()), + Value::Duration { val, .. } => Ok(val.to_string()), + Value::Date { val, .. } => Ok(val.to_rfc3339_opts(chrono::SecondsFormat::Nanos, true)), + Value::Range { val, .. } => Ok(val.to_string()), + Value::Record { val, .. } => Ok(format!( + "{{{}}}", + val.iter() + .map(|(x, y)| match y.coerce_into_string_all() { + Ok(value) => format!("{x}: {value}"), + Err(err) => format!("Error: {err}"), + }) + .collect::>() + .join(", ") + )), + Value::List { vals, .. } => Ok(format!( + "[{}]", + vals.iter() + .map(|x| match x.coerce_into_string_all() { + Ok(value) => value, + Err(err) => format!("Error: {err}"), + }) + .collect::>() + .join(", ") + )), + Value::Binary { val, .. } => match String::from_utf8(val.to_vec()) { + Ok(s) => Ok(s), + Err(err) => Value::binary(err.into_bytes(), span).cant_convert_to("string"), + }, + Value::CellPath { val, .. } => Ok(val.to_string()), + Value::Nothing { .. } => Ok("nothing".to_string()), + val => val.cant_convert_to("string"), + } + } +} + #[cfg(test)] mod test { #[test]