1use nu_cli::{add_cli_context, gather_parent_env_vars};
2use nu_cmd_lang::create_default_context;
3use nu_command::add_shell_command_context;
4use nu_engine::eval_block_with_early_return;
5use nu_parser::parse;
6use nu_protocol::engine::Command;
7use nu_protocol::format_cli_error;
8use nu_protocol::{
9 debugger::WithoutDebug,
10 engine::{Closure, EngineState, Redirection, Stack, StateWorkingSet},
11 OutDest, PipelineData, ShellError, Signals, Span, Value,
12};
13use std::sync::{atomic::AtomicBool, Arc};
14
15use crate::commands::{MjCommand, ResponseStartCommand, ReverseProxyCommand, StaticCommand, ToSse};
16use crate::Error;
17
18#[derive(Clone)]
19pub struct Engine {
20 pub state: EngineState,
21 pub closure: Option<Closure>,
22}
23
24impl Engine {
25 pub fn new() -> Result<Self, Error> {
26 let mut engine_state = create_default_context();
27
28 engine_state = add_shell_command_context(engine_state);
29 engine_state = add_cli_context(engine_state);
30 engine_state = nu_cmd_extra::extra::add_extra_command_context(engine_state);
31
32 let init_cwd = std::env::current_dir()?;
33 gather_parent_env_vars(&mut engine_state, init_cwd.as_ref());
34
35 Ok(Self {
36 state: engine_state,
37 closure: None,
38 })
39 }
40
41 pub fn add_commands(&mut self, commands: Vec<Box<dyn Command>>) -> Result<(), Error> {
42 let mut working_set = StateWorkingSet::new(&self.state);
43 for command in commands {
44 working_set.add_decl(command);
45 }
46 self.state.merge_delta(working_set.render())?;
47 Ok(())
48 }
49
50 pub fn parse_closure(&mut self, script: &str) -> Result<(), Error> {
51 let mut working_set = StateWorkingSet::new(&self.state);
52 let block = parse(&mut working_set, None, script.as_bytes(), false);
53
54 if let Some(err) = working_set.parse_errors.first() {
56 let shell_error = ShellError::GenericError {
57 error: "Parse error".into(),
58 msg: format!("{err:?}"),
59 span: Some(err.span()),
60 help: None,
61 inner: vec![],
62 };
63 return Err(Error::from(format_cli_error(
64 &working_set,
65 &shell_error,
66 None,
67 )));
68 }
69
70 if let Some(err) = working_set.compile_errors.first() {
72 let shell_error = ShellError::GenericError {
73 error: format!("Compile error {err}"),
74 msg: "".into(),
75 span: None,
76 help: None,
77 inner: vec![],
78 };
79 return Err(Error::from(format_cli_error(
80 &working_set,
81 &shell_error,
82 None,
83 )));
84 }
85
86 self.state.merge_delta(working_set.render())?;
87
88 let mut stack = Stack::new();
89 let result = eval_block_with_early_return::<WithoutDebug>(
90 &self.state,
91 &mut stack,
92 &block,
93 PipelineData::empty(),
94 )
95 .map_err(|err| {
96 let working_set = StateWorkingSet::new(&self.state);
97 Error::from(format_cli_error(&working_set, &err, None))
98 })?;
99
100 let closure = result
101 .body
102 .into_value(Span::unknown())
103 .map_err(|err| {
104 let working_set = StateWorkingSet::new(&self.state);
105 Error::from(format_cli_error(&working_set, &err, None))
106 })?
107 .into_closure()
108 .map_err(|err| {
109 let working_set = StateWorkingSet::new(&self.state);
110 Error::from(format_cli_error(&working_set, &err, None))
111 })?;
112
113 let block = self.state.get_block(closure.block_id);
115 if block.signature.required_positional.len() != 1 {
116 return Err(format!(
117 "Closure must accept exactly one request argument, found {}",
118 block.signature.required_positional.len()
119 )
120 .into());
121 }
122
123 self.state.merge_env(&mut stack)?;
124
125 self.closure = Some(closure);
126 Ok(())
127 }
128
129 pub fn set_signals(&mut self, interrupt: Arc<AtomicBool>) {
131 self.state.set_signals(Signals::new(interrupt));
132 }
133
134 pub fn eval(&self, input: Value, pipeline_data: PipelineData) -> Result<PipelineData, Error> {
135 let closure = self.closure.as_ref().ok_or("Closure not parsed")?;
136
137 let mut stack = Stack::new();
138 let mut stack =
139 stack.push_redirection(Some(Redirection::Pipe(OutDest::PipeSeparate)), None);
140 let block = self.state.get_block(closure.block_id);
141
142 stack.add_var(
143 block.signature.required_positional[0].var_id.unwrap(),
144 input,
145 );
146
147 eval_block_with_early_return::<WithoutDebug>(&self.state, &mut stack, block, pipeline_data)
148 .map(|exec_data| exec_data.body)
149 .map_err(|err| {
150 let working_set = StateWorkingSet::new(&self.state);
151 Error::from(format_cli_error(&working_set, &err, None))
152 })
153 }
154
155 pub fn add_custom_commands(&mut self) -> Result<(), Error> {
157 self.add_commands(vec![
158 Box::new(ResponseStartCommand::new()),
159 Box::new(ReverseProxyCommand::new()),
160 Box::new(StaticCommand::new()),
161 Box::new(ToSse {}),
162 Box::new(MjCommand::new()),
163 ])
164 }
165}
166
167pub fn script_to_engine(base: &Engine, script: &str) -> Option<Engine> {
170 let mut engine = base.clone();
171
172 if let Err(e) = engine.parse_closure(script) {
173 let err_str = e.to_string();
174 eprintln!("{err_str}");
175 println!(
176 "{}",
177 serde_json::json!({
178 "stamp": scru128::new(),
179 "message": "error",
180 "error": nu_utils::strip_ansi_string_likely(err_str)
181 })
182 );
183 return None;
184 }
185
186 Some(engine)
187}