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 = if let Some(name_bytes) = engine_state.find_decl_name(call.decl_id, &[]) {
66 String::from_utf8_lossy(name_bytes)
67 } else {
68 return Err(ShellError::NushellFailedSpanned {
69 msg: "known external name not found".to_string(),
70 label: "could not find name for this command".to_string(),
71 span: call.head,
72 });
73 };
74
75 let extern_name: Vec<_> = extern_name.split(' ').collect();
76
77 match &call.inner {
78 CallImpl::AstRef(call) => {
79 let extern_call = ast_call_to_extern_call(engine_state, call, &extern_name)?;
80 command.run(engine_state, stack, &(&extern_call).into(), input)
81 }
82 CallImpl::AstBox(call) => {
83 let extern_call = ast_call_to_extern_call(engine_state, call, &extern_name)?;
84 command.run(engine_state, stack, &(&extern_call).into(), input)
85 }
86 CallImpl::IrRef(call) => {
87 let extern_call = ir_call_to_extern_call(stack, call, &extern_name)?;
88 command.run(engine_state, stack, &(&extern_call).into(), input)
89 }
90 CallImpl::IrBox(call) => {
91 let extern_call = ir_call_to_extern_call(stack, call, &extern_name)?;
92 command.run(engine_state, stack, &(&extern_call).into(), input)
93 }
94 }
95 }
96
97 fn attributes(&self) -> Vec<(String, Value)> {
98 self.attributes.clone()
99 }
100
101 fn examples(&self) -> Vec<Example<'_>> {
102 self.examples
103 .iter()
104 .map(CustomExample::to_example)
105 .collect()
106 }
107}
108
109fn ast_call_to_extern_call(
111 engine_state: &EngineState,
112 call: &ast::Call,
113 extern_name: &[&str],
114) -> Result<ast::Call, ShellError> {
115 let head_span = call.head;
116
117 let mut extern_call = ast::Call::new(head_span);
118
119 let call_head_id = engine_state
120 .find_span_id(call.head)
121 .unwrap_or(UNKNOWN_SPAN_ID);
122
123 let arg_extern_name = Expression::new_existing(
124 Expr::String(extern_name[0].to_string()),
125 call.head,
126 call_head_id,
127 Type::String,
128 );
129
130 extern_call.add_positional(arg_extern_name);
131
132 for subcommand in extern_name.iter().skip(1) {
133 extern_call.add_positional(Expression::new_existing(
134 Expr::String(subcommand.to_string()),
135 call.head,
136 call_head_id,
137 Type::String,
138 ));
139 }
140
141 for arg in &call.arguments {
142 match arg {
143 ast::Argument::Positional(positional) => extern_call.add_positional(positional.clone()),
144 ast::Argument::Named(named) => {
145 let named_span_id = engine_state
146 .find_span_id(named.0.span)
147 .unwrap_or(UNKNOWN_SPAN_ID);
148 if let Some(short) = &named.1 {
149 extern_call.add_positional(Expression::new_existing(
150 Expr::String(format!("-{}", short.item)),
151 named.0.span,
152 named_span_id,
153 Type::String,
154 ));
155 } else {
156 extern_call.add_positional(Expression::new_existing(
157 Expr::String(format!("--{}", named.0.item)),
158 named.0.span,
159 named_span_id,
160 Type::String,
161 ));
162 }
163 if let Some(arg) = &named.2 {
164 extern_call.add_positional(arg.clone());
165 }
166 }
167 ast::Argument::Unknown(unknown) => extern_call.add_unknown(unknown.clone()),
168 ast::Argument::Spread(args) => extern_call.add_spread(args.clone()),
169 }
170 }
171
172 Ok(extern_call)
173}
174
175fn ir_call_to_extern_call(
177 stack: &mut Stack,
178 call: &ir::Call,
179 extern_name: &[&str],
180) -> Result<ir::Call, ShellError> {
181 let mut extern_call = ir::Call::build(call.decl_id, call.head);
182
183 for name in extern_name {
185 extern_call.add_positional(stack, call.head, Value::string(*name, call.head));
186 }
187
188 for index in 0..call.args_len {
190 match &call.arguments(stack)[index] {
191 engine::Argument::Flag {
192 data,
193 name,
194 short,
195 span,
196 } => {
197 let name_arg = engine::Argument::Positional {
198 span: *span,
199 val: Value::string(known_external_option_name(data, *name, *short), *span),
200 ast: None,
201 };
202 extern_call.add_argument(stack, name_arg);
203 }
204 engine::Argument::Named {
205 data,
206 name,
207 short,
208 span,
209 val,
210 ..
211 } => {
212 let name_arg = engine::Argument::Positional {
213 span: *span,
214 val: Value::string(known_external_option_name(data, *name, *short), *span),
215 ast: None,
216 };
217 let val_arg = engine::Argument::Positional {
218 span: *span,
219 val: val.clone(),
220 ast: None,
221 };
222 extern_call.add_argument(stack, name_arg);
223 extern_call.add_argument(stack, val_arg);
224 }
225 a @ (engine::Argument::Positional { .. }
226 | engine::Argument::Spread { .. }
227 | engine::Argument::ParserInfo { .. }) => {
228 let argument = a.clone();
229 extern_call.add_argument(stack, argument);
230 }
231 }
232 }
233
234 Ok(extern_call.finish())
235}
236
237fn known_external_option_name(data: &[u8], name: DataSlice, short: DataSlice) -> String {
238 if !data[name].is_empty() {
239 format!(
240 "--{}",
241 std::str::from_utf8(&data[name]).expect("invalid utf-8 in flag name")
242 )
243 } else {
244 format!(
245 "-{}",
246 std::str::from_utf8(&data[short]).expect("invalid utf-8 in flag short name")
247 )
248 }
249}