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