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:
51 "explain {|| ls | sort-by name type --ignore-case | get name } | table --expand",
52 result: None,
53 }]
54 }
55}
56
57pub fn get_pipeline_elements(
58 engine_state: &EngineState,
59 stack: &mut Stack,
60 block: &Block,
61 span: Span,
62) -> Vec<Value> {
63 let eval_expression = get_eval_expression(engine_state);
64
65 block
66 .pipelines
67 .iter()
68 .enumerate()
69 .flat_map(|(p_idx, pipeline)| {
70 pipeline
71 .elements
72 .iter()
73 .enumerate()
74 .map(move |(e_idx, element)| (format!("{p_idx}_{e_idx}"), element))
75 })
76 .map(move |(cmd_index, element)| {
77 let expression = &element.expr;
78 let expr_span = element.expr.span;
79
80 let (command_name, command_args_value, ty) = if let Expr::Call(call) = &expression.expr
81 {
82 let command = engine_state.get_decl(call.decl_id);
83 (
84 command.name().to_string(),
85 get_arguments(engine_state, stack, call.as_ref(), eval_expression),
86 command.signature().get_output_type().to_string(),
87 )
88 } else {
89 ("no-op".to_string(), vec![], expression.ty.to_string())
90 };
91
92 let record = record! {
93 "cmd_index" => Value::string(cmd_index, span),
94 "cmd_name" => Value::string(command_name, expr_span),
95 "type" => Value::string(ty, span),
96 "cmd_args" => Value::list(command_args_value, expr_span),
97 "span_start" => Value::int(expr_span.start as i64, span),
98 "span_end" => Value::int(expr_span.end as i64, span),
99 };
100
101 Value::record(record, expr_span)
102 })
103 .collect()
104}
105
106fn get_arguments(
107 engine_state: &EngineState,
108 stack: &mut Stack,
109 call: &ast::Call,
110 eval_expression_fn: fn(&EngineState, &mut Stack, &Expression) -> Result<Value, ShellError>,
111) -> Vec<Value> {
112 let mut arg_value = vec![];
113 let span = Span::test_data();
114 for arg in &call.arguments {
115 match arg {
116 Argument::Named((name, short, opt_expr)) => {
119 let arg_type = "named";
120 let arg_value_name = name.item.clone();
121 let arg_value_name_span_start = name.span.start as i64;
122 let arg_value_name_span_end = name.span.end as i64;
123
124 let record = record! {
125 "arg_type" => Value::string(arg_type, span),
126 "name" => Value::string(arg_value_name, name.span),
127 "type" => Value::string("string", span),
128 "span_start" => Value::int(arg_value_name_span_start, span),
129 "span_end" => Value::int(arg_value_name_span_end, span),
130 };
131 arg_value.push(Value::record(record, name.span));
132
133 if let Some(shortcut) = short {
134 let arg_type = "short";
135 let arg_value_name = shortcut.item.clone();
136 let arg_value_name_span_start = shortcut.span.start as i64;
137 let arg_value_name_span_end = shortcut.span.end as i64;
138
139 let record = record! {
140 "arg_type" => Value::string(arg_type, span),
141 "name" => Value::string(arg_value_name, shortcut.span),
142 "type" => Value::string("string", span),
143 "span_start" => Value::int(arg_value_name_span_start, span),
144 "span_end" => Value::int(arg_value_name_span_end, span),
145 };
146 arg_value.push(Value::record(record, name.span));
147 };
148
149 if let Some(expression) = opt_expr {
150 let evaluated_expression = get_expression_as_value(
151 engine_state,
152 stack,
153 expression,
154 eval_expression_fn,
155 );
156 let arg_type = "expr";
157 let arg_value_name =
158 debug_string_without_formatting(engine_state, &evaluated_expression);
159 let arg_value_type = &evaluated_expression.get_type().to_string();
160 let evaled_span = evaluated_expression.span();
161 let arg_value_name_span_start = evaled_span.start as i64;
162 let arg_value_name_span_end = evaled_span.end as i64;
163
164 let record = record! {
165 "arg_type" => Value::string(arg_type, span),
166 "name" => Value::string(arg_value_name, expression.span),
167 "type" => Value::string(arg_value_type, span),
168 "span_start" => Value::int(arg_value_name_span_start, span),
169 "span_end" => Value::int(arg_value_name_span_end, span),
170 };
171 arg_value.push(Value::record(record, expression.span));
172 };
173 }
174 Argument::Positional(inner_expr) => {
175 let arg_type = "positional";
176 let evaluated_expression =
177 get_expression_as_value(engine_state, stack, inner_expr, eval_expression_fn);
178 let arg_value_name =
179 debug_string_without_formatting(engine_state, &evaluated_expression);
180 let arg_value_type = &evaluated_expression.get_type().to_string();
181 let evaled_span = evaluated_expression.span();
182 let arg_value_name_span_start = evaled_span.start as i64;
183 let arg_value_name_span_end = evaled_span.end as i64;
184
185 let record = record! {
186 "arg_type" => Value::string(arg_type, span),
187 "name" => Value::string(arg_value_name, inner_expr.span),
188 "type" => Value::string(arg_value_type, span),
189 "span_start" => Value::int(arg_value_name_span_start, span),
190 "span_end" => Value::int(arg_value_name_span_end, span),
191 };
192 arg_value.push(Value::record(record, inner_expr.span));
193 }
194 Argument::Unknown(inner_expr) => {
195 let arg_type = "unknown";
196 let evaluated_expression =
197 get_expression_as_value(engine_state, stack, inner_expr, eval_expression_fn);
198 let arg_value_name =
199 debug_string_without_formatting(engine_state, &evaluated_expression);
200 let arg_value_type = &evaluated_expression.get_type().to_string();
201 let evaled_span = evaluated_expression.span();
202 let arg_value_name_span_start = evaled_span.start as i64;
203 let arg_value_name_span_end = evaled_span.end as i64;
204
205 let record = record! {
206 "arg_type" => Value::string(arg_type, span),
207 "name" => Value::string(arg_value_name, inner_expr.span),
208 "type" => Value::string(arg_value_type, span),
209 "span_start" => Value::int(arg_value_name_span_start, span),
210 "span_end" => Value::int(arg_value_name_span_end, span),
211 };
212 arg_value.push(Value::record(record, inner_expr.span));
213 }
214 Argument::Spread(inner_expr) => {
215 let arg_type = "spread";
216 let evaluated_expression =
217 get_expression_as_value(engine_state, stack, inner_expr, eval_expression_fn);
218 let arg_value_name =
219 debug_string_without_formatting(engine_state, &evaluated_expression);
220 let arg_value_type = &evaluated_expression.get_type().to_string();
221 let evaled_span = evaluated_expression.span();
222 let arg_value_name_span_start = evaled_span.start as i64;
223 let arg_value_name_span_end = evaled_span.end as i64;
224
225 let record = record! {
226 "arg_type" => Value::string(arg_type, span),
227 "name" => Value::string(arg_value_name, inner_expr.span),
228 "type" => Value::string(arg_value_type, span),
229 "span_start" => Value::int(arg_value_name_span_start, span),
230 "span_end" => Value::int(arg_value_name_span_end, span),
231 };
232 arg_value.push(Value::record(record, inner_expr.span));
233 }
234 };
235 }
236
237 arg_value
238}
239
240fn get_expression_as_value(
241 engine_state: &EngineState,
242 stack: &mut Stack,
243 inner_expr: &Expression,
244 eval_expression_fn: fn(&EngineState, &mut Stack, &Expression) -> Result<Value, ShellError>,
245) -> Value {
246 match eval_expression_fn(engine_state, stack, inner_expr) {
247 Ok(v) => v,
248 Err(error) => Value::error(error, inner_expr.span),
249 }
250}
251
252pub fn debug_string_without_formatting(engine_state: &EngineState, value: &Value) -> String {
253 match value {
254 Value::Bool { val, .. } => val.to_string(),
255 Value::Int { val, .. } => val.to_string(),
256 Value::Float { val, .. } => val.to_string(),
257 Value::Filesize { val, .. } => val.to_string(),
258 Value::Duration { val, .. } => val.to_string(),
259 Value::Date { val, .. } => format!("{val:?}"),
260 Value::Range { val, .. } => val.to_string(),
261 Value::String { val, .. } => val.clone(),
262 Value::Glob { val, .. } => val.clone(),
263 Value::List { vals: val, .. } => format!(
264 "[{}]",
265 val.iter()
266 .map(|v| debug_string_without_formatting(engine_state, v))
267 .collect::<Vec<_>>()
268 .join(" ")
269 ),
270 Value::Record { val, .. } => format!(
271 "{{{}}}",
272 val.iter()
273 .map(|(x, y)| format!(
274 "{}: {}",
275 x,
276 debug_string_without_formatting(engine_state, y)
277 ))
278 .collect::<Vec<_>>()
279 .join(" ")
280 ),
281 Value::Closure { val, .. } => {
282 let block = engine_state.get_block(val.block_id);
283 if let Some(span) = block.span {
284 let contents_bytes = engine_state.get_span_contents(span);
285 let contents_string = String::from_utf8_lossy(contents_bytes);
286 contents_string.to_string()
287 } else {
288 String::new()
289 }
290 }
291 Value::Nothing { .. } => String::new(),
292 Value::Error { error, .. } => format!("{error:?}"),
293 Value::Binary { val, .. } => format!("{val:?}"),
294 Value::CellPath { val, .. } => val.to_string(),
295 Value::Custom { val, .. } => val
298 .to_base_value(value.span())
299 .map(|val| debug_string_without_formatting(engine_state, &val))
300 .unwrap_or_else(|_| format!("<{}>", val.type_name())),
301 }
302}