1use nu_engine::command_prelude::*;
2use nu_protocol::{Config, PipelineMetadata, Span};
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: r#"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: r#"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: r#"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: r#"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: r#"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: r#"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: r#"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: r#"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::GenericError {
96 error: "Cannot view int value".to_string(),
97 msg: "the block does not have a viewable span".to_string(),
98 span: Some(arg_span),
99 help: None,
100 inner: vec![],
101 })
102 }
103 } else {
104 Err(ShellError::GenericError {
105 error: format!("Block Id {} does not exist", arg.coerce_into_string()?),
106 msg: "this number does not correspond to a block".to_string(),
107 span: Some(arg_span),
108 help: None,
109 inner: vec![],
110 })
111 }
112 }
113
114 Value::String { val, .. } => {
115 if let Some(decl_id) = engine_state.find_decl(val.as_bytes(), &[]) {
116 let decl = engine_state.get_decl(decl_id);
118 let sig = decl.signature();
119 let vec_of_required = &sig.required_positional;
120 let vec_of_optional = &sig.optional_positional;
121 let rest = &sig.rest_positional;
122 let vec_of_flags = &sig.named;
123 let type_signatures = &sig.input_output_types;
124
125 if decl.is_alias() {
126 if let Some(alias) = &decl.as_alias() {
127 let contents = String::from_utf8_lossy(
128 engine_state.get_span_contents(alias.wrapped_call.span),
129 );
130
131 Ok(make_output(
132 engine_state,
133 contents.to_string(),
134 Some(alias.wrapped_call.span),
135 call.head,
136 ))
137 } else {
138 Ok(make_output(
139 engine_state,
140 "no alias found".to_string(),
141 None,
142 call.head,
143 ))
144 }
145 }
146 else if let Some(block_id) = decl.block_id() {
148 let block = engine_state.get_block(block_id);
149 if let Some(block_span) = block.span {
150 let contents = engine_state.get_span_contents(block_span);
151 let mut final_contents = String::new();
153 let flags: Vec<&str> = [
155 block.redirect_env.then_some("--env"),
156 sig.allows_unknown_args.then_some("--wrapped"),
157 ]
158 .into_iter()
159 .flatten()
160 .collect();
161 let flags_str = if flags.is_empty() {
162 String::new()
163 } else {
164 format!("{} ", flags.join(" "))
165 };
166 if val.contains(' ') {
167 let _ = write!(&mut final_contents, "def {flags_str}\"{val}\" [");
168 } else {
169 let _ = write!(&mut final_contents, "def {flags_str}{val} [");
170 };
171 if !vec_of_required.is_empty()
172 || !vec_of_optional.is_empty()
173 || vec_of_flags.len() != 1
174 || rest.is_some()
175 {
176 final_contents.push(' ');
177 }
178 for n in vec_of_required {
179 let _ = write!(&mut final_contents, "{}: {} ", n.name, n.shape);
180 }
182 for n in vec_of_optional {
183 if let Some(s) = n.default_value.clone() {
184 let _ = write!(
185 &mut final_contents,
186 "{}: {} = {} ",
187 n.name,
188 n.shape,
189 s.to_expanded_string(" ", &Config::default())
190 );
191 } else {
192 let _ =
193 write!(&mut final_contents, "{}?: {} ", n.name, n.shape);
194 }
195 }
196 for n in vec_of_flags {
197 if n.long == "help" {
199 continue;
200 }
201 let _ = write!(&mut final_contents, "--{}", n.long);
202 if let Some(short) = n.short {
203 let _ = write!(&mut final_contents, "(-{short})");
204 }
205 if let Some(arg) = &n.arg {
206 let _ = write!(&mut final_contents, ": {arg}");
207 }
208 final_contents.push(' ');
209 }
210 if let Some(rest_arg) = rest {
211 let _ = write!(
212 &mut final_contents,
213 "...{}:{}",
214 rest_arg.name, rest_arg.shape
215 );
216 }
217 let len = type_signatures.len();
218 if len != 0 {
219 final_contents.push_str("]: [");
220 let mut c = 0;
221 for (insig, outsig) in type_signatures {
222 c += 1;
223 let s = format!("{insig} -> {outsig}");
224 final_contents.push_str(&s);
225 if c != len {
226 final_contents.push_str(", ")
227 }
228 }
229 }
230 final_contents.push_str("] ");
231 final_contents.push_str(&String::from_utf8_lossy(contents));
232
233 Ok(make_output(
234 engine_state,
235 final_contents,
236 Some(block_span),
237 call.head,
238 ))
239 } else {
240 Err(ShellError::GenericError {
241 error: "Cannot view string value".to_string(),
242 msg: "the command does not have a viewable block span".to_string(),
243 span: Some(arg_span),
244 help: None,
245 inner: vec![],
246 })
247 }
248 } else {
249 Err(ShellError::GenericError {
250 error: "Cannot view string decl value".to_string(),
251 msg: "the command does not have a viewable block".to_string(),
252 span: Some(arg_span),
253 help: None,
254 inner: vec![],
255 })
256 }
257 } else if let Some(module_id) = engine_state.find_module(val.as_bytes(), &[]) {
258 let module = engine_state.get_module(module_id);
260 if let Some(module_span) = module.span {
261 let contents = engine_state.get_span_contents(module_span);
262
263 Ok(make_output(
264 engine_state,
265 String::from_utf8_lossy(contents).to_string(),
266 Some(module_span),
267 call.head,
268 ))
269 } else {
270 Err(ShellError::GenericError {
271 error: "Cannot view string module value".to_string(),
272 msg: "the module does not have a viewable block".to_string(),
273 span: Some(arg_span),
274 help: None,
275 inner: vec![],
276 })
277 }
278 } else {
279 Err(ShellError::GenericError {
280 error: "Cannot view string value".to_string(),
281 msg: "this name does not correspond to a viewable value".to_string(),
282 span: Some(arg_span),
283 help: None,
284 inner: vec![],
285 })
286 }
287 }
288 value => {
289 if let Ok(closure) = value.as_closure() {
290 let block = engine_state.get_block(closure.block_id);
291
292 if let Some(span) = block.span {
293 let contents = engine_state.get_span_contents(span);
294
295 Ok(make_output(
296 engine_state,
297 String::from_utf8_lossy(contents).to_string(),
298 Some(span),
299 call.head,
300 ))
301 } else {
302 Ok(make_output(
303 engine_state,
304 "<internal command>".to_string(),
305 None,
306 call.head,
307 ))
308 }
309 } else {
310 Err(ShellError::GenericError {
311 error: "Cannot view value".to_string(),
312 msg: "this value cannot be viewed".to_string(),
313 span: Some(arg_span),
314 help: None,
315 inner: vec![],
316 })
317 }
318 }
319 }
320 }
321}
322
323fn file_for_span(engine_state: &EngineState, span: Span) -> Option<String> {
325 engine_state
326 .files()
327 .find(|f| f.covered_span.contains_span(span))
328 .map(|f| f.name.to_string())
329}
330
331fn make_output(
334 engine_state: &EngineState,
335 src: String,
336 span_opt: Option<Span>,
337 call_span: Span,
338) -> PipelineData {
339 let pd = Value::string(src, call_span).into_pipeline_data();
340
341 let mut metadata = PipelineMetadata {
342 content_type: Some("application/x-nuscript".into()),
343 ..Default::default()
344 };
345 if let Some(span) = span_opt
346 && let Some(fname) = file_for_span(engine_state, span)
347 {
348 metadata.data_source = nu_protocol::DataSource::FilePath(std::path::PathBuf::from(fname));
349 }
350
351 pd.set_metadata(Some(metadata))
352}