relocate debug commands (#8071)

# Description

Now that we've landed the debug commands we were working on, let's
relocate them to an easier place to find all of them. That's what this
PR does.

The only actual code change was changing the `timeit` command to a
`Category::Debug` command. The rest is just moving things around and
hooking them up.

# User-Facing Changes



# Tests + Formatting

Don't forget to add tests that cover your changes.

Make sure you've run and fixed any issues with these commands:

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A
clippy::needless_collect` to check that you're using the standard code
style
- `cargo test --workspace` to check that all tests pass

# After Submitting

If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
This commit is contained in:
Darren Schroeder
2023-02-13 10:39:07 -06:00
committed by GitHub
parent 8136170431
commit 4c787af26d
18 changed files with 40 additions and 34 deletions

View File

@ -0,0 +1,330 @@
use nu_engine::{eval_expression, CallExt};
use nu_protocol::ast::{Argument, Block, Call, Expr, Expression};
use nu_protocol::engine::{Closure, Command, EngineState, Stack};
use nu_protocol::{
Category, Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Span,
SyntaxShape, Type, Value,
};
#[derive(Clone)]
pub struct Explain;
impl Command for Explain {
fn name(&self) -> &str {
"explain"
}
fn usage(&self) -> &str {
"Explain closure contents."
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("explain")
.required(
"closure",
SyntaxShape::Closure(Some(vec![SyntaxShape::Any])),
"the closure to run",
)
.input_output_types(vec![(Type::Any, Type::Any), (Type::Nothing, Type::Any)])
.allow_variants_without_examples(true)
.category(Category::Debug)
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
// This was all delightfully stolen from benchmark :)
let capture_block: Closure = call.req(engine_state, stack, 0)?;
let block = engine_state.get_block(capture_block.block_id);
let ctrlc = engine_state.ctrlc.clone();
let mut stack = stack.captures_to_stack(&capture_block.captures);
let elements = get_pipeline_elements(engine_state, &mut stack, block)?;
Ok(elements.into_pipeline_data(ctrlc))
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Explain a command within a closure",
example: "explain { ls | sort-by name type -i | get name } | table -e",
result: None,
}]
}
}
pub fn get_pipeline_elements(
engine_state: &EngineState,
stack: &mut Stack,
block: &Block,
) -> Result<Vec<Value>, ShellError> {
let mut element_values = vec![];
let span = Span::test_data();
for (pipeline_idx, pipeline) in block.pipelines.iter().enumerate() {
let mut i = 0;
while i < pipeline.elements.len() {
let pipeline_element = &pipeline.elements[i];
let pipeline_expression = pipeline_element.expression().clone();
let pipeline_span = &pipeline_element.span();
let element_str =
String::from_utf8_lossy(engine_state.get_span_contents(pipeline_span));
let value = Value::string(element_str.to_string(), *pipeline_span);
let expr = pipeline_expression.expr.clone();
let (command_name, command_args_value) = if let Expr::Call(call) = expr {
let command = engine_state.get_decl(call.decl_id);
(
command.name().to_string(),
get_arguments(engine_state, stack, *call),
)
} else {
("no-op".to_string(), vec![])
};
let index = format!("{pipeline_idx}_{i}");
let value_type = value.get_type();
let value_span = value.span()?;
let value_span_start = value_span.start as i64;
let value_span_end = value_span.end as i64;
let command_name = command_name;
let rec = Value::Record {
cols: vec![
"cmd_index".to_string(),
"cmd_name".to_string(),
"type".to_string(),
"cmd_args".to_string(),
"span_start".to_string(),
"span_end".to_string(),
],
vals: vec![
Value::string(index, span),
Value::string(command_name, value_span),
Value::string(value_type.to_string(), span),
Value::List {
vals: command_args_value,
span: value_span,
},
Value::int(value_span_start, span),
Value::int(value_span_end, span),
],
span: value_span,
};
element_values.push(rec);
i += 1;
}
}
Ok(element_values)
}
fn get_arguments(engine_state: &EngineState, stack: &mut Stack, call: Call) -> Vec<Value> {
let mut arg_value = vec![];
let span = Span::test_data();
for arg in &call.arguments {
match arg {
// I think the second argument to Argument::Named is the short name, but I'm not really sure.
// Please fix it if it's wrong. :)
Argument::Named((name, short, opt_expr)) => {
let arg_type = "named";
let arg_value_name = name.item.clone();
let arg_value_name_span_start = name.span.start as i64;
let arg_value_name_span_end = name.span.end as i64;
let rec = Value::Record {
cols: vec![
"arg_type".to_string(),
"name".to_string(),
"type".to_string(),
"span_start".to_string(),
"span_end".to_string(),
],
vals: vec![
Value::string(arg_type, span),
Value::string(arg_value_name, name.span),
Value::string("string".to_string(), span),
Value::int(arg_value_name_span_start, span),
Value::int(arg_value_name_span_end, span),
],
span: name.span,
};
arg_value.push(rec);
if let Some(shortcut) = short {
let arg_type = "short";
let arg_value_name = shortcut.item.clone();
let arg_value_name_span_start = shortcut.span.start as i64;
let arg_value_name_span_end = shortcut.span.end as i64;
let rec = Value::Record {
cols: vec![
"arg_type".to_string(),
"name".to_string(),
"type".to_string(),
"span_start".to_string(),
"span_end".to_string(),
],
vals: vec![
Value::string(arg_type, span),
Value::string(arg_value_name, shortcut.span),
Value::string("string".to_string(), span),
Value::int(arg_value_name_span_start, span),
Value::int(arg_value_name_span_end, span),
],
span: name.span,
};
arg_value.push(rec);
} else {
};
if let Some(expression) = opt_expr {
let evaluated_expression =
get_expression_as_value(engine_state, stack, expression);
let arg_type = "expr";
let arg_value_name = debug_string_without_formatting(&evaluated_expression);
let arg_value_type = &evaluated_expression.get_type().to_string();
let evaled_span = evaluated_expression.expect_span();
let arg_value_name_span_start = evaled_span.start as i64;
let arg_value_name_span_end = evaled_span.end as i64;
let rec = Value::Record {
cols: vec![
"arg_type".to_string(),
"name".to_string(),
"type".to_string(),
"span_start".to_string(),
"span_end".to_string(),
],
vals: vec![
Value::string(arg_type, span),
Value::string(arg_value_name, expression.span),
Value::string(arg_value_type, span),
Value::int(arg_value_name_span_start, span),
Value::int(arg_value_name_span_end, span),
],
span: expression.span,
};
arg_value.push(rec);
} else {
};
}
Argument::Positional(inner_expr) => {
let arg_type = "positional";
let evaluated_expression = get_expression_as_value(engine_state, stack, inner_expr);
let arg_value_name = debug_string_without_formatting(&evaluated_expression);
let arg_value_type = &evaluated_expression.get_type().to_string();
let evaled_span = evaluated_expression.expect_span();
let arg_value_name_span_start = evaled_span.start as i64;
let arg_value_name_span_end = evaled_span.end as i64;
let rec = Value::Record {
cols: vec![
"arg_type".to_string(),
"name".to_string(),
"type".to_string(),
"span_start".to_string(),
"span_end".to_string(),
],
vals: vec![
Value::string(arg_type, span),
Value::string(arg_value_name, inner_expr.span),
Value::string(arg_value_type, span),
Value::int(arg_value_name_span_start, span),
Value::int(arg_value_name_span_end, span),
],
span: inner_expr.span,
};
arg_value.push(rec);
}
Argument::Unknown(inner_expr) => {
let arg_type = "unknown";
let evaluated_expression = get_expression_as_value(engine_state, stack, inner_expr);
let arg_value_name = debug_string_without_formatting(&evaluated_expression);
let arg_value_type = &evaluated_expression.get_type().to_string();
let evaled_span = evaluated_expression.expect_span();
let arg_value_name_span_start = evaled_span.start as i64;
let arg_value_name_span_end = evaled_span.end as i64;
let rec = Value::Record {
cols: vec![
"arg_type".to_string(),
"name".to_string(),
"type".to_string(),
"span_start".to_string(),
"span_end".to_string(),
],
vals: vec![
Value::string(arg_type, span),
Value::string(arg_value_name, inner_expr.span),
Value::string(arg_value_type, span),
Value::int(arg_value_name_span_start, span),
Value::int(arg_value_name_span_end, span),
],
span: inner_expr.span,
};
arg_value.push(rec);
}
};
}
arg_value
}
fn get_expression_as_value(
engine_state: &EngineState,
stack: &mut Stack,
inner_expr: &Expression,
) -> Value {
match eval_expression(engine_state, stack, inner_expr) {
Ok(v) => v,
Err(error) => Value::Error { error },
}
}
pub fn debug_string_without_formatting(value: &Value) -> String {
match value {
Value::Bool { val, .. } => val.to_string(),
Value::Int { val, .. } => val.to_string(),
Value::Float { val, .. } => val.to_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::String { val, .. } => val.clone(),
Value::List { vals: val, .. } => format!(
"[{}]",
val.iter()
.map(debug_string_without_formatting)
.collect::<Vec<_>>()
.join(" ")
),
Value::Record { cols, vals, .. } => format!(
"{{{}}}",
cols.iter()
.zip(vals.iter())
.map(|(x, y)| format!("{}: {}", x, debug_string_without_formatting(y)))
.collect::<Vec<_>>()
.join(" ")
),
Value::LazyRecord { val, .. } => match val.collect() {
Ok(val) => debug_string_without_formatting(&val),
Err(error) => format!("{error:?}"),
},
//TODO: It would be good to drill in deeper to blocks and closures.
Value::Block { val, .. } => format!("<Block {val}>"),
Value::Closure { val, .. } => format!("<Closure {val}>"),
Value::Nothing { .. } => String::new(),
Value::Error { error } => format!("{error:?}"),
Value::Binary { val, .. } => format!("{val:?}"),
Value::CellPath { val, .. } => val.into_string(),
Value::CustomValue { val, .. } => val.value_string(),
}
}