Skip to main content

xs/nu/
config.rs

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
13/// Configuration parsed from a Nushell script.
14pub struct NuScriptConfig {
15    /// The main executable closure defined by the `run:` field in the script.
16    pub run_closure: Closure,
17    /// The full Nushell Value (typically a record) that the script evaluated to.
18    /// Callers can use this to extract other script-defined options.
19    pub full_config_value: Value,
20}
21
22impl NuScriptConfig {
23    /// Deserializes specific options from the `full_config_value`.
24    ///
25    /// The type `T` must implement `serde::Deserialize`.
26    /// This is a convenience for callers to extract custom fields from the script's
27    /// configuration record after `run` has been processed.
28    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/// Options for customizing the output frames
39#[derive(Clone, Debug, Serialize, Deserialize, Default)]
40pub struct ReturnOptions {
41    /// Custom suffix for output frames (default is ".out" for actors, ".recv" for commands)
42    pub suffix: Option<String>,
43    /// Optional time-to-live for the output frames
44    pub ttl: Option<TTL>,
45    /// Storage target for output: "cas" forces CAS storage, default stores records as meta
46    pub target: Option<String>,
47}
48
49/// Parse a script into a NuScriptConfig struct.
50///
51/// Parses and evaluates the script, then extracts the `run` closure and the full
52/// configuration value. VFS modules (registered via `*.nu` topics) are already
53/// available on the engine state before this function is called.
54pub 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}