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 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 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 let mut final_contents = String::new();
149 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 }
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 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 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
309fn 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
317fn 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}