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 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 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 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}