Skip to main content

nu_command/debug/
explain.rs

1use nu_engine::{command_prelude::*, get_eval_expression};
2use nu_protocol::{
3    ast::{self, Argument, Block, Expr, Expression},
4    engine::Closure,
5};
6
7#[derive(Clone)]
8pub struct Explain;
9
10impl Command for Explain {
11    fn name(&self) -> &str {
12        "explain"
13    }
14
15    fn description(&self) -> &str {
16        "Explain closure contents."
17    }
18
19    fn signature(&self) -> nu_protocol::Signature {
20        Signature::build("explain")
21            .required(
22                "closure",
23                SyntaxShape::Closure(Some(vec![SyntaxShape::Any])),
24                "The closure to run.",
25            )
26            .input_output_types(vec![(Type::Any, Type::Any), (Type::Nothing, Type::Any)])
27            .allow_variants_without_examples(true)
28            .category(Category::Debug)
29    }
30
31    fn run(
32        &self,
33        engine_state: &EngineState,
34        stack: &mut Stack,
35        call: &Call,
36        _input: PipelineData,
37    ) -> Result<PipelineData, ShellError> {
38        let head = call.head;
39        // This was all delightfully stolen from benchmark :)
40        let capture_block: Closure = call.req(engine_state, stack, 0)?;
41        let block = engine_state.get_block(capture_block.block_id);
42        let mut stack = stack.captures_to_stack(capture_block.captures);
43        let elements = get_pipeline_elements(engine_state, &mut stack, block, head);
44        Ok(Value::list(elements, head).into_pipeline_data())
45    }
46
47    fn examples(&self) -> Vec<Example<'_>> {
48        vec![Example {
49            description: "Explain a command within a closure.",
50            example: "explain {|| ls | sort-by name type --ignore-case | get name } | table --expand",
51            result: None,
52        }]
53    }
54}
55
56pub fn get_pipeline_elements(
57    engine_state: &EngineState,
58    stack: &mut Stack,
59    block: &Block,
60    span: Span,
61) -> Vec<Value> {
62    let eval_expression = get_eval_expression(engine_state);
63
64    block
65        .pipelines
66        .iter()
67        .enumerate()
68        .flat_map(|(p_idx, pipeline)| {
69            pipeline
70                .elements
71                .iter()
72                .enumerate()
73                .map(move |(e_idx, element)| (format!("{p_idx}_{e_idx}"), element))
74        })
75        .map(move |(cmd_index, element)| {
76            let expression = &element.expr;
77            let expr_span = element.expr.span;
78
79            let (command_name, command_args_value, ty) = if let Expr::Call(call) = &expression.expr
80            {
81                let command = engine_state.get_decl(call.decl_id);
82                (
83                    command.name().to_string(),
84                    get_arguments(engine_state, stack, call.as_ref(), eval_expression),
85                    command
86                        .signature()
87                        .get_output_type(None)
88                        .unwrap_or_default()
89                        .to_string(),
90                )
91            } else {
92                ("no-op".to_string(), vec![], expression.ty.to_string())
93            };
94
95            let record = record! {
96                "cmd_index" => Value::string(cmd_index, span),
97                "cmd_name" => Value::string(command_name, expr_span),
98                "type" => Value::string(ty, span),
99                "cmd_args" => Value::list(command_args_value, expr_span),
100                "span_start" => Value::int(expr_span.start as i64, span),
101                "span_end" => Value::int(expr_span.end as i64, span),
102            };
103
104            Value::record(record, expr_span)
105        })
106        .collect()
107}
108
109fn get_arguments(
110    engine_state: &EngineState,
111    stack: &mut Stack,
112    call: &ast::Call,
113    eval_expression_fn: fn(&EngineState, &mut Stack, &Expression) -> Result<Value, ShellError>,
114) -> Vec<Value> {
115    let mut arg_value = vec![];
116    let span = Span::test_data();
117    for arg in &call.arguments {
118        match arg {
119            // I think the second argument to Argument::Named is the short name, but I'm not really sure.
120            // Please fix it if it's wrong. :)
121            Argument::Named((name, short, opt_expr)) => {
122                let arg_type = "named";
123                let arg_value_name = name.item.clone();
124                let arg_value_name_span_start = name.span.start as i64;
125                let arg_value_name_span_end = name.span.end as i64;
126
127                let record = record! {
128                    "arg_type" => Value::string(arg_type, span),
129                    "name" => Value::string(arg_value_name, name.span),
130                    "type" => Value::string("string", span),
131                    "span_start" => Value::int(arg_value_name_span_start, span),
132                    "span_end" => Value::int(arg_value_name_span_end, span),
133                };
134                arg_value.push(Value::record(record, name.span));
135
136                if let Some(shortcut) = short {
137                    let arg_type = "short";
138                    let arg_value_name = shortcut.item.clone();
139                    let arg_value_name_span_start = shortcut.span.start as i64;
140                    let arg_value_name_span_end = shortcut.span.end as i64;
141
142                    let record = record! {
143                        "arg_type" => Value::string(arg_type, span),
144                        "name" => Value::string(arg_value_name, shortcut.span),
145                        "type" => Value::string("string", span),
146                        "span_start" => Value::int(arg_value_name_span_start, span),
147                        "span_end" => Value::int(arg_value_name_span_end, span),
148                    };
149                    arg_value.push(Value::record(record, name.span));
150                };
151
152                if let Some(expression) = opt_expr {
153                    let evaluated_expression = get_expression_as_value(
154                        engine_state,
155                        stack,
156                        expression,
157                        eval_expression_fn,
158                    );
159                    let arg_type = "expr";
160                    let arg_value_name =
161                        debug_string_without_formatting(engine_state, &evaluated_expression);
162                    let arg_value_type = &evaluated_expression.get_type().to_string();
163                    let evaled_span = evaluated_expression.span();
164                    let arg_value_name_span_start = evaled_span.start as i64;
165                    let arg_value_name_span_end = evaled_span.end as i64;
166
167                    let record = record! {
168                        "arg_type" => Value::string(arg_type, span),
169                        "name" => Value::string(arg_value_name, expression.span),
170                        "type" => Value::string(arg_value_type, span),
171                        "span_start" => Value::int(arg_value_name_span_start, span),
172                        "span_end" => Value::int(arg_value_name_span_end, span),
173                    };
174                    arg_value.push(Value::record(record, expression.span));
175                };
176            }
177            Argument::Positional(inner_expr) => {
178                let arg_type = "positional";
179                let evaluated_expression =
180                    get_expression_as_value(engine_state, stack, inner_expr, eval_expression_fn);
181                let arg_value_name =
182                    debug_string_without_formatting(engine_state, &evaluated_expression);
183                let arg_value_type = &evaluated_expression.get_type().to_string();
184                let evaled_span = evaluated_expression.span();
185                let arg_value_name_span_start = evaled_span.start as i64;
186                let arg_value_name_span_end = evaled_span.end as i64;
187
188                let record = record! {
189                    "arg_type" => Value::string(arg_type, span),
190                    "name" => Value::string(arg_value_name, inner_expr.span),
191                    "type" => Value::string(arg_value_type, span),
192                    "span_start" => Value::int(arg_value_name_span_start, span),
193                    "span_end" => Value::int(arg_value_name_span_end, span),
194                };
195                arg_value.push(Value::record(record, inner_expr.span));
196            }
197            Argument::Unknown(inner_expr) => {
198                let arg_type = "unknown";
199                let evaluated_expression =
200                    get_expression_as_value(engine_state, stack, inner_expr, eval_expression_fn);
201                let arg_value_name =
202                    debug_string_without_formatting(engine_state, &evaluated_expression);
203                let arg_value_type = &evaluated_expression.get_type().to_string();
204                let evaled_span = evaluated_expression.span();
205                let arg_value_name_span_start = evaled_span.start as i64;
206                let arg_value_name_span_end = evaled_span.end as i64;
207
208                let record = record! {
209                    "arg_type" => Value::string(arg_type, span),
210                    "name" => Value::string(arg_value_name, inner_expr.span),
211                    "type" => Value::string(arg_value_type, span),
212                    "span_start" => Value::int(arg_value_name_span_start, span),
213                    "span_end" => Value::int(arg_value_name_span_end, span),
214                };
215                arg_value.push(Value::record(record, inner_expr.span));
216            }
217            Argument::Spread(inner_expr) => {
218                let arg_type = "spread";
219                let evaluated_expression =
220                    get_expression_as_value(engine_state, stack, inner_expr, eval_expression_fn);
221                let arg_value_name =
222                    debug_string_without_formatting(engine_state, &evaluated_expression);
223                let arg_value_type = &evaluated_expression.get_type().to_string();
224                let evaled_span = evaluated_expression.span();
225                let arg_value_name_span_start = evaled_span.start as i64;
226                let arg_value_name_span_end = evaled_span.end as i64;
227
228                let record = record! {
229                    "arg_type" => Value::string(arg_type, span),
230                    "name" => Value::string(arg_value_name, inner_expr.span),
231                    "type" => Value::string(arg_value_type, span),
232                    "span_start" => Value::int(arg_value_name_span_start, span),
233                    "span_end" => Value::int(arg_value_name_span_end, span),
234                };
235                arg_value.push(Value::record(record, inner_expr.span));
236            }
237        };
238    }
239
240    arg_value
241}
242
243fn get_expression_as_value(
244    engine_state: &EngineState,
245    stack: &mut Stack,
246    inner_expr: &Expression,
247    eval_expression_fn: fn(&EngineState, &mut Stack, &Expression) -> Result<Value, ShellError>,
248) -> Value {
249    match eval_expression_fn(engine_state, stack, inner_expr) {
250        Ok(v) => v,
251        Err(error) => Value::error(error, inner_expr.span),
252    }
253}
254
255pub fn debug_string_without_formatting(engine_state: &EngineState, value: &Value) -> String {
256    match value {
257        Value::Bool { val, .. } => val.to_string(),
258        Value::Int { val, .. } => val.to_string(),
259        Value::Float { val, .. } => val.to_string(),
260        Value::Filesize { val, .. } => val.to_string(),
261        Value::Duration { val, .. } => val.to_string(),
262        Value::Date { val, .. } => format!("{val:?}"),
263        Value::Range { val, .. } => val.to_string(),
264        Value::String { val, .. } => val.clone(),
265        Value::Glob { val, .. } => val.clone(),
266        Value::List { vals: val, .. } => format!(
267            "[{}]",
268            val.iter()
269                .map(|v| debug_string_without_formatting(engine_state, v))
270                .collect::<Vec<_>>()
271                .join(" ")
272        ),
273        Value::Record { val, .. } => format!(
274            "{{{}}}",
275            val.iter()
276                .map(|(x, y)| format!(
277                    "{}: {}",
278                    x,
279                    debug_string_without_formatting(engine_state, y)
280                ))
281                .collect::<Vec<_>>()
282                .join(" ")
283        ),
284        Value::Closure { val, .. } => {
285            let block = engine_state.get_block(val.block_id);
286            if let Some(span) = block.span {
287                let contents_bytes = engine_state.get_span_contents(span);
288                let contents_string = String::from_utf8_lossy(contents_bytes);
289                contents_string.to_string()
290            } else {
291                String::new()
292            }
293        }
294        Value::Nothing { .. } => String::new(),
295        Value::Error { error, .. } => format!("{error:?}"),
296        Value::Binary { val, .. } => format!("{val:?}"),
297        Value::CellPath { val, .. } => val.to_string(),
298        // If we fail to collapse the custom value, just print <{type_name}> - failure is not
299        // that critical here
300        Value::Custom { val, .. } => val
301            .to_base_value(value.span())
302            .map(|val| debug_string_without_formatting(engine_state, &val))
303            .unwrap_or_else(|_| format!("<{}>", val.type_name())),
304    }
305}