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