1use std::path::Path;
2use std::sync::{atomic::AtomicBool, Arc};
3
4use tokio_util::sync::CancellationToken;
5
6use nu_cli::{add_cli_context, gather_parent_env_vars};
7use nu_cmd_lang::create_default_context;
8use nu_command::add_shell_command_context;
9use nu_engine::eval_block_with_early_return;
10use nu_parser::parse;
11use nu_plugin_engine::{GetPlugin, PluginDeclaration};
12use nu_protocol::engine::Command;
13use nu_protocol::format_cli_error;
14use nu_protocol::{
15 debugger::WithoutDebug,
16 engine::{Closure, EngineState, Redirection, Stack, StateWorkingSet},
17 OutDest, PipelineData, PluginIdentity, RegisteredPlugin, ShellError, Signals, Span, Type,
18 Value,
19};
20
21use crate::commands::{
22 HighlightCommand, HighlightLangCommand, HighlightThemeCommand, MdCommand, MjCommand,
23 MjCompileCommand, MjRenderCommand, PrintCommand, ReverseProxyCommand, StaticCommand, ToSse,
24};
25use crate::logging::log_error;
26use crate::stdlib::load_http_nu_stdlib;
27use crate::Error;
28
29#[derive(Clone)]
30pub struct Engine {
31 pub state: EngineState,
32 pub closure: Option<Closure>,
33 pub reload_token: CancellationToken,
35}
36
37impl Engine {
38 pub fn new() -> Result<Self, Error> {
39 let mut engine_state = create_default_context();
40
41 engine_state = add_shell_command_context(engine_state);
42 engine_state = add_cli_context(engine_state);
43 engine_state = nu_cmd_extra::extra::add_extra_command_context(engine_state);
44
45 load_http_nu_stdlib(&mut engine_state)?;
46
47 let init_cwd = std::env::current_dir()?;
48 gather_parent_env_vars(&mut engine_state, init_cwd.as_ref());
49
50 Ok(Self {
51 state: engine_state,
52 closure: None,
53 reload_token: CancellationToken::new(),
54 })
55 }
56
57 pub fn set_http_nu_env(&mut self, dev: bool) -> Result<(), Error> {
59 let mut stack = Stack::new();
60 let span = Span::unknown();
61 let record = Value::record(
62 nu_protocol::record! {
63 "dev" => Value::bool(dev, span),
64 },
65 span,
66 );
67 stack.add_env_var("HTTP_NU".to_string(), record);
68 self.state.merge_env(&mut stack)?;
69 Ok(())
70 }
71
72 pub fn add_commands(&mut self, commands: Vec<Box<dyn Command>>) -> Result<(), Error> {
73 let mut working_set = StateWorkingSet::new(&self.state);
74 for command in commands {
75 working_set.add_decl(command);
76 }
77 self.state.merge_delta(working_set.render())?;
78 Ok(())
79 }
80
81 pub fn load_plugin(&mut self, path: &Path) -> Result<(), Error> {
83 let path = path.canonicalize().map_err(|e| {
85 Error::from(format!("Failed to canonicalize plugin path {path:?}: {e}"))
86 })?;
87
88 let identity = PluginIdentity::new(&path, None).map_err(|_| {
90 Error::from(format!(
91 "Invalid plugin path {path:?}: must be named nu_plugin_*"
92 ))
93 })?;
94
95 let mut working_set = StateWorkingSet::new(&self.state);
96
97 let plugin = nu_plugin_engine::add_plugin_to_working_set(&mut working_set, &identity)?;
99
100 self.state.merge_delta(working_set.render())?;
102
103 let interface = plugin.clone().get_plugin(None)?;
105
106 plugin.set_metadata(Some(interface.get_metadata()?));
108
109 let mut working_set = StateWorkingSet::new(&self.state);
111 for signature in interface.get_signature()? {
112 let decl = PluginDeclaration::new(plugin.clone(), signature);
113 working_set.add_decl(Box::new(decl));
114 }
115 self.state.merge_delta(working_set.render())?;
116
117 Ok(())
118 }
119
120 pub fn parse_closure(&mut self, script: &str) -> Result<(), Error> {
121 let mut working_set = StateWorkingSet::new(&self.state);
122 let block = parse(&mut working_set, None, script.as_bytes(), false);
123
124 if let Some(err) = working_set.parse_errors.first() {
126 let shell_error = ShellError::GenericError {
127 error: "Parse error".into(),
128 msg: format!("{err:?}"),
129 span: Some(err.span()),
130 help: None,
131 inner: vec![],
132 };
133 return Err(Error::from(format_cli_error(
134 None,
135 &working_set,
136 &shell_error,
137 None,
138 )));
139 }
140
141 if let Some(err) = working_set.compile_errors.first() {
143 let shell_error = ShellError::GenericError {
144 error: format!("Compile error {err}"),
145 msg: "".into(),
146 span: None,
147 help: None,
148 inner: vec![],
149 };
150 return Err(Error::from(format_cli_error(
151 None,
152 &working_set,
153 &shell_error,
154 None,
155 )));
156 }
157
158 self.state.merge_delta(working_set.render())?;
159
160 let mut stack = Stack::new();
161 let result = eval_block_with_early_return::<WithoutDebug>(
162 &self.state,
163 &mut stack,
164 &block,
165 PipelineData::empty(),
166 )
167 .map_err(|err| {
168 let working_set = StateWorkingSet::new(&self.state);
169 Error::from(format_cli_error(None, &working_set, &err, None))
170 })?;
171
172 let closure = result
173 .body
174 .into_value(Span::unknown())
175 .map_err(|err| {
176 let working_set = StateWorkingSet::new(&self.state);
177 Error::from(format_cli_error(None, &working_set, &err, None))
178 })?
179 .into_closure()
180 .map_err(|err| {
181 let working_set = StateWorkingSet::new(&self.state);
182 Error::from(format_cli_error(None, &working_set, &err, None))
183 })?;
184
185 let block = self.state.get_block(closure.block_id);
187 if block.signature.required_positional.len() != 1 {
188 return Err(format!(
189 "Closure must accept exactly one request argument, found {}",
190 block.signature.required_positional.len()
191 )
192 .into());
193 }
194
195 self.state.merge_env(&mut stack)?;
196
197 self.closure = Some(closure);
198 Ok(())
199 }
200
201 pub fn set_signals(&mut self, interrupt: Arc<AtomicBool>) {
203 self.state.set_signals(Signals::new(interrupt));
204 }
205
206 pub fn set_lib_dirs(&mut self, paths: &[std::path::PathBuf]) -> Result<(), Error> {
208 if paths.is_empty() {
209 return Ok(());
210 }
211 let span = Span::unknown();
212 let vals: Vec<Value> = paths
213 .iter()
214 .map(|p| Value::string(p.to_string_lossy(), span))
215 .collect();
216
217 let mut working_set = StateWorkingSet::new(&self.state);
218 let var_id = working_set.add_variable(
219 b"$NU_LIB_DIRS".into(),
220 span,
221 Type::List(Box::new(Type::String)),
222 false,
223 );
224 working_set.set_variable_const_val(var_id, Value::list(vals, span));
225 self.state.merge_delta(working_set.render())?;
226 Ok(())
227 }
228
229 pub fn eval(&self, script: &str) -> Result<Value, Error> {
231 let mut working_set = StateWorkingSet::new(&self.state);
232 let block = parse(&mut working_set, None, script.as_bytes(), false);
233
234 if let Some(err) = working_set.parse_errors.first() {
235 let shell_error = ShellError::GenericError {
236 error: "Parse error".into(),
237 msg: format!("{err:?}"),
238 span: Some(err.span()),
239 help: None,
240 inner: vec![],
241 };
242 return Err(Error::from(format_cli_error(
243 None,
244 &working_set,
245 &shell_error,
246 None,
247 )));
248 }
249
250 if let Some(err) = working_set.compile_errors.first() {
251 let shell_error = ShellError::GenericError {
252 error: format!("Compile error {err}"),
253 msg: "".into(),
254 span: None,
255 help: None,
256 inner: vec![],
257 };
258 return Err(Error::from(format_cli_error(
259 None,
260 &working_set,
261 &shell_error,
262 None,
263 )));
264 }
265
266 let mut engine_state = self.state.clone();
268 engine_state.merge_delta(working_set.render())?;
269
270 let mut stack = Stack::new();
271 let result = eval_block_with_early_return::<WithoutDebug>(
272 &engine_state,
273 &mut stack,
274 &block,
275 PipelineData::empty(),
276 )
277 .map_err(|err| {
278 let working_set = StateWorkingSet::new(&engine_state);
279 Error::from(format_cli_error(None, &working_set, &err, None))
280 })?;
281
282 result.body.into_value(Span::unknown()).map_err(|err| {
283 let working_set = StateWorkingSet::new(&engine_state);
284 Error::from(format_cli_error(None, &working_set, &err, None))
285 })
286 }
287
288 pub fn run_closure(
290 &self,
291 input: Value,
292 pipeline_data: PipelineData,
293 ) -> Result<PipelineData, Error> {
294 let closure = self.closure.as_ref().ok_or("Closure not parsed")?;
295
296 let mut stack = Stack::new().captures_to_stack(closure.captures.clone());
297 let mut stack =
298 stack.push_redirection(Some(Redirection::Pipe(OutDest::PipeSeparate)), None);
299 let block = self.state.get_block(closure.block_id);
300
301 stack.add_var(
302 block.signature.required_positional[0].var_id.unwrap(),
303 input,
304 );
305
306 eval_block_with_early_return::<WithoutDebug>(&self.state, &mut stack, block, pipeline_data)
307 .map(|exec_data| exec_data.body)
308 .map_err(|err| {
309 let working_set = StateWorkingSet::new(&self.state);
310 Error::from(format_cli_error(None, &working_set, &err, None))
311 })
312 }
313
314 pub fn add_custom_commands(&mut self) -> Result<(), Error> {
316 self.add_commands(vec![
317 Box::new(ReverseProxyCommand::new()),
318 Box::new(StaticCommand::new()),
319 Box::new(ToSse {}),
320 Box::new(MjCommand::new()),
321 Box::new(MjCompileCommand::new()),
322 Box::new(MjRenderCommand::new()),
323 Box::new(HighlightCommand::new()),
324 Box::new(HighlightThemeCommand::new()),
325 Box::new(HighlightLangCommand::new()),
326 Box::new(MdCommand::new()),
327 Box::new(PrintCommand::new()),
328 ])
329 }
330
331 #[cfg(feature = "cross-stream")]
333 pub fn add_store_commands(&mut self, store: &xs::store::Store) -> Result<(), Error> {
334 self.add_commands(vec![
335 Box::new(xs::nu::commands::cat_stream_command::CatStreamCommand::new(
336 store.clone(),
337 )),
338 Box::new(xs::nu::commands::append_command::AppendCommand::new(
339 store.clone(),
340 serde_json::json!({}),
341 )),
342 Box::new(xs::nu::commands::cas_command::CasCommand::new(
343 store.clone(),
344 )),
345 Box::new(xs::nu::commands::last_stream_command::LastStreamCommand::new(store.clone())),
346 Box::new(xs::nu::commands::get_command::GetCommand::new(
347 store.clone(),
348 )),
349 Box::new(xs::nu::commands::remove_command::RemoveCommand::new(
350 store.clone(),
351 )),
352 Box::new(xs::nu::commands::scru128_command::Scru128Command::new()),
353 ])
354 }
355}
356
357pub fn script_to_engine(base: &Engine, script: &str) -> Option<Engine> {
360 let mut engine = base.clone();
361 engine.reload_token = CancellationToken::new();
363
364 if let Err(e) = engine.parse_closure(script) {
365 log_error(&nu_utils::strip_ansi_string_likely(e.to_string()));
366 return None;
367 }
368
369 Some(engine)
370}