Skip to main content

nu_command/debug/
view_source.rs

1use nu_engine::command_prelude::*;
2use nu_protocol::{Config, PipelineMetadata, Span, shell_error::generic::GenericError};
3
4use std::fmt::Write;
5
6#[derive(Clone)]
7pub struct ViewSource;
8
9impl Command for ViewSource {
10    fn name(&self) -> &str {
11        "view source"
12    }
13
14    fn description(&self) -> &str {
15        "View a block, module, or a definition."
16    }
17
18    fn signature(&self) -> nu_protocol::Signature {
19        Signature::build("view source")
20            .input_output_types(vec![(Type::Nothing, Type::String)])
21            .required("item", SyntaxShape::Any, "Name or block to view.")
22            .category(Category::Debug)
23    }
24
25    fn examples(&self) -> Vec<Example<'_>> {
26        vec![
27            Example {
28                description: "View the source of a code block.",
29                example: "let abc = {|| echo 'hi' }; view source $abc",
30                result: Some(Value::test_string("{|| echo 'hi' }")),
31            },
32            Example {
33                description: "View the source of a custom command.",
34                example: "def hi [] { echo 'Hi!' }; view source hi",
35                result: Some(Value::test_string("def hi [] { echo 'Hi!' }")),
36            },
37            Example {
38                description: "View the source of a custom command, which participates in the caller environment.",
39                example: "def --env foo [] { $env.BAR = 'BAZ' }; view source foo",
40                result: Some(Value::test_string("def --env foo [] { $env.BAR = 'BAZ' }")),
41            },
42            Example {
43                description: "View the source of a custom command with flags.",
44                example: "def --wrapped --env foo [...rest: string] { print $rest }; view source foo",
45                result: Some(Value::test_string(
46                    "def --env --wrapped foo [ ...rest: string] { print $rest }",
47                )),
48            },
49            Example {
50                description: "View the source of a custom command with flags and arguments.",
51                example: "def test [a?:any --b:int ...rest:string] { echo 'test' }; view source test",
52                result: Some(Value::test_string(
53                    "def test [ a?: any --b: int ...rest: string] { echo 'test' }",
54                )),
55            },
56            Example {
57                description: "View the source of a module.",
58                example: "module mod-foo { export-env { $env.FOO_ENV = 'BAZ' } }; view source mod-foo",
59                result: Some(Value::test_string(" export-env { $env.FOO_ENV = 'BAZ' }")),
60            },
61            Example {
62                description: "View the source of an alias.",
63                example: "alias hello = echo hi; view source hello",
64                result: Some(Value::test_string("echo hi")),
65            },
66            Example {
67                description: "View the file where a definition lives via metadata.",
68                example: "view source some_command | metadata",
69                result: None,
70            },
71        ]
72    }
73
74    fn run(
75        &self,
76        engine_state: &EngineState,
77        stack: &mut Stack,
78        call: &Call,
79        _input: PipelineData,
80    ) -> Result<PipelineData, ShellError> {
81        let arg: Value = call.req(engine_state, stack, 0)?;
82        let arg_span = arg.span();
83
84        match arg {
85            Value::Int { val, .. } => {
86                if let Some(block) =
87                    engine_state.try_get_block(nu_protocol::BlockId::new(val as usize))
88                {
89                    if let Some(span) = block.span {
90                        let contents = engine_state.get_span_contents(span);
91                        let src = String::from_utf8_lossy(contents).to_string();
92
93                        Ok(make_output(engine_state, src, Some(span), call.head))
94                    } else {
95                        Err(ShellError::Generic(GenericError::new(
96                            "Cannot view int value",
97                            "the block does not have a viewable span",
98                            arg_span,
99                        )))
100                    }
101                } else {
102                    Err(ShellError::Generic(GenericError::new(
103                        format!("Block Id {} does not exist", arg.coerce_into_string()?),
104                        "this number does not correspond to a block",
105                        arg_span,
106                    )))
107                }
108            }
109
110            Value::String { val, .. } => {
111                if let Some(decl_id) = engine_state.find_decl(val.as_bytes(), &[]) {
112                    // arg is a command
113                    let decl = engine_state.get_decl(decl_id);
114                    let sig = decl.signature();
115                    let vec_of_required = &sig.required_positional;
116                    let vec_of_optional = &sig.optional_positional;
117                    let rest = &sig.rest_positional;
118                    let vec_of_flags = &sig.named;
119                    let type_signatures = &sig.input_output_types;
120
121                    if decl.is_alias() {
122                        if let Some(alias) = &decl.as_alias() {
123                            let contents = String::from_utf8_lossy(
124                                engine_state.get_span_contents(alias.wrapped_call.span),
125                            );
126
127                            Ok(make_output(
128                                engine_state,
129                                contents.to_string(),
130                                Some(alias.wrapped_call.span),
131                                call.head,
132                            ))
133                        } else {
134                            Ok(make_output(
135                                engine_state,
136                                "no alias found".to_string(),
137                                None,
138                                call.head,
139                            ))
140                        }
141                    }
142                    // gets vector of positionals.
143                    else if let Some(block_id) = decl.block_id() {
144                        let block = engine_state.get_block(block_id);
145                        if let Some(block_span) = block.span {
146                            let contents = engine_state.get_span_contents(block_span);
147                            // name of function
148                            let mut final_contents = String::new();
149                            // Collect def flags based on block and signature properties
150                            let flags: Vec<&str> = [
151                                block.redirect_env.then_some("--env"),
152                                sig.allows_unknown_args.then_some("--wrapped"),
153                            ]
154                            .into_iter()
155                            .flatten()
156                            .collect();
157                            let flags_str = if flags.is_empty() {
158                                String::new()
159                            } else {
160                                format!("{} ", flags.join(" "))
161                            };
162                            if val.contains(' ') {
163                                let _ = write!(&mut final_contents, "def {flags_str}\"{val}\" [");
164                            } else {
165                                let _ = write!(&mut final_contents, "def {flags_str}{val} [");
166                            };
167                            if !vec_of_required.is_empty()
168                                || !vec_of_optional.is_empty()
169                                || vec_of_flags.len() != 1
170                                || rest.is_some()
171                            {
172                                final_contents.push(' ');
173                            }
174                            for n in vec_of_required {
175                                let _ = write!(&mut final_contents, "{}: {} ", n.name, n.shape);
176                                // positional arguments
177                            }
178                            for n in vec_of_optional {
179                                if let Some(s) = n.default_value.clone() {
180                                    let _ = write!(
181                                        &mut final_contents,
182                                        "{}: {} = {} ",
183                                        n.name,
184                                        n.shape,
185                                        s.to_expanded_string(" ", &Config::default())
186                                    );
187                                } else {
188                                    let _ =
189                                        write!(&mut final_contents, "{}?: {} ", n.name, n.shape);
190                                }
191                            }
192                            for n in vec_of_flags {
193                                // skip adding the help flag
194                                if n.long == "help" {
195                                    continue;
196                                }
197                                let _ = write!(&mut final_contents, "--{}", n.long);
198                                if let Some(short) = n.short {
199                                    let _ = write!(&mut final_contents, "(-{short})");
200                                }
201                                if let Some(arg) = &n.arg {
202                                    let _ = write!(&mut final_contents, ": {arg}");
203                                }
204                                final_contents.push(' ');
205                            }
206                            if let Some(rest_arg) = rest {
207                                let _ = write!(
208                                    &mut final_contents,
209                                    "...{}:{}",
210                                    rest_arg.name, rest_arg.shape
211                                );
212                            }
213                            let len = type_signatures.len();
214                            if len != 0 {
215                                final_contents.push_str("]: [");
216                                let mut c = 0;
217                                for (insig, outsig) in type_signatures {
218                                    c += 1;
219                                    let s = format!("{insig} -> {outsig}");
220                                    final_contents.push_str(&s);
221                                    if c != len {
222                                        final_contents.push_str(", ")
223                                    }
224                                }
225                            }
226                            final_contents.push_str("] ");
227                            final_contents.push_str(&String::from_utf8_lossy(contents));
228
229                            Ok(make_output(
230                                engine_state,
231                                final_contents,
232                                Some(block_span),
233                                call.head,
234                            ))
235                        } else {
236                            Err(ShellError::Generic(GenericError::new(
237                                "Cannot view string value",
238                                "the command does not have a viewable block span",
239                                arg_span,
240                            )))
241                        }
242                    } else {
243                        Err(ShellError::Generic(GenericError::new(
244                            "Cannot view string decl value",
245                            "the command does not have a viewable block",
246                            arg_span,
247                        )))
248                    }
249                } else if let Some(module_id) = engine_state.find_module(val.as_bytes(), &[]) {
250                    // arg is a module
251                    let module = engine_state.get_module(module_id);
252                    if let Some(module_span) = module.span {
253                        let contents = engine_state.get_span_contents(module_span);
254
255                        Ok(make_output(
256                            engine_state,
257                            String::from_utf8_lossy(contents).to_string(),
258                            Some(module_span),
259                            call.head,
260                        ))
261                    } else {
262                        Err(ShellError::Generic(GenericError::new(
263                            "Cannot view string module value",
264                            "the module does not have a viewable block",
265                            arg_span,
266                        )))
267                    }
268                } else {
269                    Err(ShellError::Generic(GenericError::new(
270                        "Cannot view string value",
271                        "this name does not correspond to a viewable value",
272                        arg_span,
273                    )))
274                }
275            }
276            value => {
277                if let Ok(closure) = value.as_closure() {
278                    let block = engine_state.get_block(closure.block_id);
279
280                    if let Some(span) = block.span {
281                        let contents = engine_state.get_span_contents(span);
282
283                        Ok(make_output(
284                            engine_state,
285                            String::from_utf8_lossy(contents).to_string(),
286                            Some(span),
287                            call.head,
288                        ))
289                    } else {
290                        Ok(make_output(
291                            engine_state,
292                            "<internal command>".to_string(),
293                            None,
294                            call.head,
295                        ))
296                    }
297                } else {
298                    Err(ShellError::Generic(GenericError::new(
299                        "Cannot view value",
300                        "this value cannot be viewed",
301                        arg_span,
302                    )))
303                }
304            }
305        }
306    }
307}
308
309// Helper function to find the file path associated with a given span, if any.
310fn file_for_span(engine_state: &EngineState, span: Span) -> Option<String> {
311    engine_state
312        .files()
313        .find(|f| f.covered_span.contains_span(span))
314        .map(|f| f.name.to_string())
315}
316
317// Helper function to wrap source text into a string value and record the file path in
318// datasource metadata when available
319fn make_output(
320    engine_state: &EngineState,
321    src: String,
322    span_opt: Option<Span>,
323    call_span: Span,
324) -> PipelineData {
325    let pd = Value::string(src, call_span).into_pipeline_data();
326
327    let mut metadata = PipelineMetadata {
328        content_type: Some("application/x-nuscript".into()),
329        ..Default::default()
330    };
331    if let Some(span) = span_opt
332        && let Some(fname) = file_for_span(engine_state, span)
333    {
334        metadata.data_source = nu_protocol::DataSource::FilePath(std::path::PathBuf::from(fname));
335    }
336
337    pd.set_metadata(Some(metadata))
338}