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 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 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 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 }
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 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 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}