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 if let Some(short) = &named.1 {
156 extern_call.add_positional(Expression::new_existing(
157 Expr::String(format!("-{}", short.item)),
158 named.0.span,
159 named_span_id,
160 Type::String,
161 ));
162 } else {
163 extern_call.add_positional(Expression::new_existing(
164 Expr::String(format!("--{}", named.0.item)),
165 named.0.span,
166 named_span_id,
167 Type::String,
168 ));
169 }
170 if let Some(arg) = &named.2 {
171 extern_call.add_positional(arg.clone());
172 }
173 }
174 ast::Argument::Unknown(unknown) => extern_call.add_unknown(unknown.clone()),
175 ast::Argument::Spread(args) => extern_call.add_spread(args.clone()),
176 }
177 }
178
179 Ok(extern_call)
180}
181
182fn ir_call_to_extern_call(
184 stack: &mut Stack,
185 call: &ir::Call,
186 extern_name: &[&str],
187) -> Result<ir::Call, ShellError> {
188 let mut extern_call = ir::Call::build(call.decl_id, call.head);
189
190 for name in extern_name {
192 extern_call.add_positional(stack, call.head, Value::string(*name, call.head));
193 }
194
195 for index in 0..call.args_len {
197 match &call.arguments(stack)[index] {
198 engine::Argument::Flag {
199 data,
200 name,
201 short,
202 span,
203 } => {
204 let name_arg = engine::Argument::Positional {
205 span: *span,
206 val: Value::string(known_external_option_name(data, *name, *short), *span),
207 ast: None,
208 };
209 extern_call.add_argument(stack, name_arg);
210 }
211 engine::Argument::Named {
212 data,
213 name,
214 short,
215 span,
216 val,
217 ..
218 } => {
219 let name_arg = engine::Argument::Positional {
220 span: *span,
221 val: Value::string(known_external_option_name(data, *name, *short), *span),
222 ast: None,
223 };
224 let val_arg = engine::Argument::Positional {
225 span: *span,
226 val: val.clone(),
227 ast: None,
228 };
229 extern_call.add_argument(stack, name_arg);
230 extern_call.add_argument(stack, val_arg);
231 }
232 a @ (engine::Argument::Positional { .. }
233 | engine::Argument::Spread { .. }
234 | engine::Argument::ParserInfo { .. }) => {
235 let argument = a.clone();
236 extern_call.add_argument(stack, argument);
237 }
238 }
239 }
240
241 Ok(extern_call.finish())
242}
243
244fn known_external_option_name(data: &[u8], name: DataSlice, short: DataSlice) -> String {
245 if !data[name].is_empty() {
246 format!(
247 "--{}",
248 std::str::from_utf8(&data[name]).expect("invalid utf-8 in flag name")
249 )
250 } else {
251 format!(
252 "-{}",
253 std::str::from_utf8(&data[short]).expect("invalid utf-8 in flag short name")
254 )
255 }
256}