1use nu_engine::command_prelude::*;
2use nu_protocol::{
3 CustomExample,
4 ast::{self, Expr, Expression},
5 engine::{self, CallImpl, CommandType, UNKNOWN_SPAN_ID},
6 ir::{self, DataSlice},
7};
8
9#[derive(Clone)]
10pub struct KnownExternal {
11 pub signature: Box<Signature>,
12 pub attributes: Vec<(String, Value)>,
13 pub examples: Vec<CustomExample>,
14 pub span: Span,
16}
17
18impl Command for KnownExternal {
19 fn name(&self) -> &str {
20 &self.signature.name
21 }
22
23 fn signature(&self) -> Signature {
24 *self.signature.clone()
25 }
26
27 fn description(&self) -> &str {
28 &self.signature.description
29 }
30
31 fn extra_description(&self) -> &str {
32 &self.signature.extra_description
33 }
34
35 fn search_terms(&self) -> Vec<&str> {
36 self.signature
37 .search_terms
38 .iter()
39 .map(String::as_str)
40 .collect()
41 }
42
43 fn command_type(&self) -> CommandType {
44 CommandType::External
45 }
46
47 fn decl_span(&self) -> Option<Span> {
48 Some(self.span)
49 }
50
51 fn run(
52 &self,
53 engine_state: &EngineState,
54 stack: &mut Stack,
55 call: &Call,
56 input: PipelineData,
57 ) -> Result<PipelineData, ShellError> {
58 let head_span = call.head;
59 let decl_id = engine_state
60 .find_decl("run-external".as_bytes(), &[])
61 .ok_or(ShellError::ExternalNotSupported { span: head_span })?;
62
63 let command = engine_state.get_decl(decl_id);
64
65 let extern_name_str =
68 if let Some(name_bytes) = engine_state.find_decl_name(call.decl_id, &[]) {
69 String::from_utf8_lossy(name_bytes).into_owned()
70 } else {
71 self.name().to_string()
72 };
73
74 let extern_name: Vec<_> = extern_name_str.split_whitespace().collect();
75
76 if extern_name.is_empty() {
77 return Err(ShellError::NushellFailedSpanned {
78 msg: "known external name not found".to_string(),
79 label: "could not find name for this command".to_string(),
80 span: call.head,
81 });
82 }
83
84 match &call.inner {
85 CallImpl::AstRef(call) => {
86 let extern_call = ast_call_to_extern_call(engine_state, call, &extern_name)?;
87 command.run(engine_state, stack, &(&extern_call).into(), input)
88 }
89 CallImpl::AstBox(call) => {
90 let extern_call = ast_call_to_extern_call(engine_state, call, &extern_name)?;
91 command.run(engine_state, stack, &(&extern_call).into(), input)
92 }
93 CallImpl::IrRef(call) => {
94 let extern_call = ir_call_to_extern_call(stack, call, &extern_name)?;
95 command.run(engine_state, stack, &(&extern_call).into(), input)
96 }
97 CallImpl::IrBox(call) => {
98 let extern_call = ir_call_to_extern_call(stack, call, &extern_name)?;
99 command.run(engine_state, stack, &(&extern_call).into(), input)
100 }
101 }
102 }
103
104 fn attributes(&self) -> Vec<(String, Value)> {
105 self.attributes.clone()
106 }
107
108 fn examples(&self) -> Vec<Example<'_>> {
109 self.examples
110 .iter()
111 .map(CustomExample::to_example)
112 .collect()
113 }
114}
115
116fn ast_call_to_extern_call(
118 engine_state: &EngineState,
119 call: &ast::Call,
120 extern_name: &[&str],
121) -> Result<ast::Call, ShellError> {
122 let head_span = call.head;
123
124 let mut extern_call = ast::Call::new(head_span);
125
126 let call_head_id = engine_state
127 .find_span_id(call.head)
128 .unwrap_or(UNKNOWN_SPAN_ID);
129
130 let arg_extern_name = Expression::new_existing(
131 Expr::String(extern_name[0].to_string()),
132 call.head,
133 call_head_id,
134 Type::String,
135 );
136
137 extern_call.add_positional(arg_extern_name);
138
139 for subcommand in extern_name.iter().skip(1) {
140 extern_call.add_positional(Expression::new_existing(
141 Expr::String(subcommand.to_string()),
142 call.head,
143 call_head_id,
144 Type::String,
145 ));
146 }
147
148 for arg in &call.arguments {
149 match arg {
150 ast::Argument::Positional(positional) => extern_call.add_positional(positional.clone()),
151 ast::Argument::Named(named) => {
152 let named_span_id = engine_state
153 .find_span_id(named.0.span)
154 .unwrap_or(UNKNOWN_SPAN_ID);
155 let flag = match named.1.as_ref().filter(|short| !short.item.is_empty()) {
157 Some(short) => format!("-{}", short.item),
158 None => format!("--{}", named.0.item),
159 };
160 extern_call.add_positional(Expression::new_existing(
161 Expr::String(flag),
162 named.0.span,
163 named_span_id,
164 Type::String,
165 ));
166 if let Some(arg) = &named.2 {
167 extern_call.add_positional(arg.clone());
168 }
169 }
170 ast::Argument::Unknown(unknown) => extern_call.add_unknown(unknown.clone()),
171 ast::Argument::Spread(args) => extern_call.add_spread(args.clone()),
172 }
173 }
174
175 Ok(extern_call)
176}
177
178fn ir_call_to_extern_call(
180 stack: &mut Stack,
181 call: &ir::Call,
182 extern_name: &[&str],
183) -> Result<ir::Call, ShellError> {
184 let mut extern_call = ir::Call::build(call.decl_id, call.head);
185
186 for name in extern_name {
188 extern_call.add_positional(stack, call.head, Value::string(*name, call.head));
189 }
190
191 for index in 0..call.args_len {
193 match &call.arguments(stack)[index] {
194 engine::Argument::Flag {
195 data,
196 name,
197 short,
198 span,
199 } => {
200 let name_arg = engine::Argument::Positional {
201 span: *span,
202 val: Value::string(known_external_option_name(data, *name, *short), *span),
203 ast: None,
204 };
205 extern_call.add_argument(stack, name_arg);
206 }
207 engine::Argument::Named {
208 data,
209 name,
210 short,
211 span,
212 val,
213 ..
214 } => {
215 let name_arg = engine::Argument::Positional {
216 span: *span,
217 val: Value::string(known_external_option_name(data, *name, *short), *span),
218 ast: None,
219 };
220 let val_arg = engine::Argument::Positional {
221 span: *span,
222 val: val.clone(),
223 ast: None,
224 };
225 extern_call.add_argument(stack, name_arg);
226 extern_call.add_argument(stack, val_arg);
227 }
228 a @ (engine::Argument::Positional { .. }
229 | engine::Argument::Spread { .. }
230 | engine::Argument::ParserInfo { .. }) => {
231 let argument = a.clone();
232 extern_call.add_argument(stack, argument);
233 }
234 }
235 }
236
237 Ok(extern_call.finish())
238}
239
240fn known_external_option_name(data: &[u8], name: DataSlice, short: DataSlice) -> String {
241 if !data[name].is_empty() {
242 format!(
243 "--{}",
244 std::str::from_utf8(&data[name]).expect("invalid utf-8 in flag name")
245 )
246 } else {
247 format!(
248 "-{}",
249 std::str::from_utf8(&data[short]).expect("invalid utf-8 in flag short name")
250 )
251 }
252}