1use nu_engine::eval_block_with_early_return;
2use nu_parser::parse;
3use nu_protocol::debugger::WithoutDebug;
4use nu_protocol::engine::{Closure, Stack, StateWorkingSet};
5use nu_protocol::shell_error::generic::GenericError;
6use nu_protocol::{PipelineData, ShellError, Span, Value};
7
8use serde::{Deserialize, Serialize};
9
10use crate::error::Error;
11use crate::nu::util::value_to_json;
12use crate::store::TTL;
13
14pub struct NuScriptConfig {
16 pub run_closure: Closure,
18 pub full_config_value: Value,
21}
22
23impl NuScriptConfig {
24 pub fn deserialize_options<T>(&self) -> Result<T, Error>
30 where
31 T: for<'de> serde::Deserialize<'de>,
32 {
33 let json_value = value_to_json(&self.full_config_value);
34 serde_json::from_value(json_value)
35 .map_err(|e| format!("Failed to deserialize script options: {e}").into())
36 }
37}
38
39#[derive(Clone, Debug, Serialize, Deserialize, Default)]
41pub struct ReturnOptions {
42 pub suffix: Option<String>,
44 pub ttl: Option<TTL>,
46 pub target: Option<String>,
48}
49
50pub fn parse_config(engine: &mut crate::nu::Engine, script: &str) -> Result<NuScriptConfig, Error> {
56 let mut working_set = StateWorkingSet::new(&engine.state);
57 let block = parse(&mut working_set, None, script.as_bytes(), false);
58
59 if let Some(err) = working_set.parse_errors.first() {
60 let shell_error = ShellError::Generic(GenericError::new(
61 "Parse error in script",
62 format!("{err:?}"),
63 err.span(),
64 ));
65 return Err(Error::from(nu_protocol::format_cli_error(
66 None,
67 &working_set,
68 &shell_error,
69 None,
70 )));
71 }
72
73 if let Some(err) = working_set.compile_errors.first() {
74 let shell_error = ShellError::Generic(GenericError::new_internal(
75 "Compile error in script",
76 format!("{err:?}"),
77 ));
78 return Err(Error::from(nu_protocol::format_cli_error(
79 None,
80 &working_set,
81 &shell_error,
82 None,
83 )));
84 }
85
86 engine.state.merge_delta(working_set.render())?;
87
88 let mut stack = Stack::new();
89 let eval_result = eval_block_with_early_return::<WithoutDebug>(
90 &engine.state,
91 &mut stack,
92 &block,
93 PipelineData::empty(),
94 )
95 .map_err(|err| {
96 let working_set = StateWorkingSet::new(&engine.state);
97 Error::from(nu_protocol::format_cli_error(
98 None,
99 &working_set,
100 &err,
101 None,
102 ))
103 })?;
104
105 let config_value = eval_result.body.into_value(Span::unknown())?;
106
107 let run_val = config_value
108 .get_data_by_key("run")
109 .ok_or_else(|| -> Error { "Script must define a 'run' closure.".into() })?;
110 let run_closure = run_val
111 .as_closure()
112 .map_err(|e| -> Error { format!("'run' field must be a closure: {e}").into() })?;
113
114 engine.state.merge_env(&mut stack)?;
115
116 Ok(NuScriptConfig {
117 run_closure: run_closure.clone(),
118 full_config_value: config_value,
119 })
120}