Skip to main content

nu_parser/
known_external.rs

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    /// The span of the `extern` declaration, used to look up its source file.
15    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
109/// Transform the args from an `ast::Call` onto a `run-external` call
110fn 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
175/// Transform the args from an `ir::Call` onto a `run-external` call
176fn 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    // Add the command and subcommands
184    for name in extern_name {
185        extern_call.add_positional(stack, call.head, Value::string(*name, call.head));
186    }
187
188    // Add the arguments, reformatting named arguments into string positionals
189    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}