nu_command/debug/
view_source.rs

1use nu_engine::command_prelude::*;
2use nu_protocol::{Config, DataSource, PipelineMetadata};
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 run(
26        &self,
27        engine_state: &EngineState,
28        stack: &mut Stack,
29        call: &Call,
30        _input: PipelineData,
31    ) -> Result<PipelineData, ShellError> {
32        let arg: Value = call.req(engine_state, stack, 0)?;
33        let arg_span = arg.span();
34
35        let source = match arg {
36            Value::Int { val, .. } => {
37                if let Some(block) =
38                    engine_state.try_get_block(nu_protocol::BlockId::new(val as usize))
39                {
40                    if let Some(span) = block.span {
41                        let contents = engine_state.get_span_contents(span);
42                        Ok(Value::string(String::from_utf8_lossy(contents), call.head)
43                            .into_pipeline_data())
44                    } else {
45                        Err(ShellError::GenericError {
46                            error: "Cannot view int value".to_string(),
47                            msg: "the block does not have a viewable span".to_string(),
48                            span: Some(arg_span),
49                            help: None,
50                            inner: vec![],
51                        })
52                    }
53                } else {
54                    Err(ShellError::GenericError {
55                        error: format!("Block Id {} does not exist", arg.coerce_into_string()?),
56                        msg: "this number does not correspond to a block".to_string(),
57                        span: Some(arg_span),
58                        help: None,
59                        inner: vec![],
60                    })
61                }
62            }
63
64            Value::String { val, .. } => {
65                if let Some(decl_id) = engine_state.find_decl(val.as_bytes(), &[]) {
66                    // arg is a command
67                    let decl = engine_state.get_decl(decl_id);
68                    let sig = decl.signature();
69                    let vec_of_required = &sig.required_positional;
70                    let vec_of_optional = &sig.optional_positional;
71                    let rest = &sig.rest_positional;
72                    let vec_of_flags = &sig.named;
73                    let type_signatures = &sig.input_output_types;
74
75                    if decl.is_alias() {
76                        if let Some(alias) = &decl.as_alias() {
77                            let contents = String::from_utf8_lossy(
78                                engine_state.get_span_contents(alias.wrapped_call.span),
79                            );
80                            Ok(Value::string(contents, call.head).into_pipeline_data())
81                        } else {
82                            Ok(Value::string("no alias found", call.head).into_pipeline_data())
83                        }
84                    }
85                    // gets vector of positionals.
86                    else if let Some(block_id) = decl.block_id() {
87                        let block = engine_state.get_block(block_id);
88                        if let Some(block_span) = block.span {
89                            let contents = engine_state.get_span_contents(block_span);
90                            // name of function
91                            let mut final_contents = String::new();
92                            if val.contains(' ') {
93                                let _ = write!(&mut final_contents, "def \"{val}\" [");
94                            } else {
95                                let _ = write!(&mut final_contents, "def {val} [");
96                            };
97                            if !vec_of_required.is_empty()
98                                || !vec_of_optional.is_empty()
99                                || vec_of_flags.len() != 1
100                                || rest.is_some()
101                            {
102                                final_contents.push(' ');
103                            }
104                            for n in vec_of_required {
105                                let _ = write!(&mut final_contents, "{}: {} ", n.name, n.shape);
106                                // positional arguments
107                            }
108                            for n in vec_of_optional {
109                                if let Some(s) = n.default_value.clone() {
110                                    let _ = write!(
111                                        &mut final_contents,
112                                        "{}: {} = {} ",
113                                        n.name,
114                                        n.shape,
115                                        s.to_expanded_string(" ", &Config::default())
116                                    );
117                                } else {
118                                    let _ =
119                                        write!(&mut final_contents, "{}?: {} ", n.name, n.shape);
120                                }
121                            }
122                            for n in vec_of_flags {
123                                // skip adding the help flag
124                                if n.long == "help" {
125                                    continue;
126                                }
127                                let _ = write!(&mut final_contents, "--{}", n.long);
128                                if let Some(short) = n.short {
129                                    let _ = write!(&mut final_contents, "(-{})", short);
130                                }
131                                if let Some(arg) = &n.arg {
132                                    let _ = write!(&mut final_contents, ": {}", arg);
133                                }
134                                final_contents.push(' ');
135                            }
136                            if let Some(rest_arg) = rest {
137                                let _ = write!(
138                                    &mut final_contents,
139                                    "...{}:{}",
140                                    rest_arg.name, rest_arg.shape
141                                );
142                            }
143                            let len = type_signatures.len();
144                            if len != 0 {
145                                final_contents.push_str("]: [");
146                                let mut c = 0;
147                                for (insig, outsig) in type_signatures {
148                                    c += 1;
149                                    let s = format!("{} -> {}", insig, outsig);
150                                    final_contents.push_str(&s);
151                                    if c != len {
152                                        final_contents.push_str(", ")
153                                    }
154                                }
155                            }
156                            final_contents.push_str("] ");
157                            final_contents.push_str(&String::from_utf8_lossy(contents));
158                            Ok(Value::string(final_contents, call.head).into_pipeline_data())
159                        } else {
160                            Err(ShellError::GenericError {
161                                error: "Cannot view string value".to_string(),
162                                msg: "the command does not have a viewable block span".to_string(),
163                                span: Some(arg_span),
164                                help: None,
165                                inner: vec![],
166                            })
167                        }
168                    } else {
169                        Err(ShellError::GenericError {
170                            error: "Cannot view string decl value".to_string(),
171                            msg: "the command does not have a viewable block".to_string(),
172                            span: Some(arg_span),
173                            help: None,
174                            inner: vec![],
175                        })
176                    }
177                } else if let Some(module_id) = engine_state.find_module(val.as_bytes(), &[]) {
178                    // arg is a module
179                    let module = engine_state.get_module(module_id);
180                    if let Some(module_span) = module.span {
181                        let contents = engine_state.get_span_contents(module_span);
182                        Ok(Value::string(String::from_utf8_lossy(contents), call.head)
183                            .into_pipeline_data())
184                    } else {
185                        Err(ShellError::GenericError {
186                            error: "Cannot view string module value".to_string(),
187                            msg: "the module does not have a viewable block".to_string(),
188                            span: Some(arg_span),
189                            help: None,
190                            inner: vec![],
191                        })
192                    }
193                } else {
194                    Err(ShellError::GenericError {
195                        error: "Cannot view string value".to_string(),
196                        msg: "this name does not correspond to a viewable value".to_string(),
197                        span: Some(arg_span),
198                        help: None,
199                        inner: vec![],
200                    })
201                }
202            }
203            value => {
204                if let Ok(closure) = value.as_closure() {
205                    let block = engine_state.get_block(closure.block_id);
206
207                    if let Some(span) = block.span {
208                        let contents = engine_state.get_span_contents(span);
209                        Ok(Value::string(String::from_utf8_lossy(contents), call.head)
210                            .into_pipeline_data())
211                    } else {
212                        Ok(Value::string("<internal command>", call.head).into_pipeline_data())
213                    }
214                } else {
215                    Err(ShellError::GenericError {
216                        error: "Cannot view value".to_string(),
217                        msg: "this value cannot be viewed".to_string(),
218                        span: Some(arg_span),
219                        help: None,
220                        inner: vec![],
221                    })
222                }
223            }
224        };
225        source.map(|x| {
226            x.set_metadata(Some(PipelineMetadata {
227                data_source: DataSource::None,
228                content_type: Some("application/x-nuscript".into()),
229            }))
230        })
231    }
232
233    fn examples(&self) -> Vec<Example> {
234        vec![
235            Example {
236                description: "View the source of a code block",
237                example: r#"let abc = {|| echo 'hi' }; view source $abc"#,
238                result: Some(Value::test_string("{|| echo 'hi' }")),
239            },
240            Example {
241                description: "View the source of a custom command",
242                example: r#"def hi [] { echo 'Hi!' }; view source hi"#,
243                result: Some(Value::test_string("def hi [] { echo 'Hi!' }")),
244            },
245            Example {
246                description: "View the source of a custom command, which participates in the caller environment",
247                example: r#"def --env foo [] { $env.BAR = 'BAZ' }; view source foo"#,
248                result: Some(Value::test_string("def foo [] { $env.BAR = 'BAZ' }")),
249            },
250            Example {
251                description: "View the source of a custom command with flags and arguments",
252                example: r#"def test [a?:any --b:int ...rest:string] { echo 'test' }; view source test"#,
253                result: Some(Value::test_string(
254                    "def test [ a?: any --b: int ...rest: string] { echo 'test' }",
255                )),
256            },
257            Example {
258                description: "View the source of a module",
259                example: r#"module mod-foo { export-env { $env.FOO_ENV = 'BAZ' } }; view source mod-foo"#,
260                result: Some(Value::test_string(" export-env { $env.FOO_ENV = 'BAZ' }")),
261            },
262            Example {
263                description: "View the source of an alias",
264                example: r#"alias hello = echo hi; view source hello"#,
265                result: Some(Value::test_string("echo hi")),
266            },
267        ]
268    }
269}