Skip to main content

nu_command/env/config/
config_flatten.rs

1use nu_engine::command_prelude::*;
2use nu_protocol::shell_error::generic::GenericError;
3use nu_utils::JsonFlattener; // Ensure this import is present // Ensure this import is present
4
5#[derive(Clone)]
6pub struct ConfigFlatten;
7
8impl Command for ConfigFlatten {
9    fn name(&self) -> &str {
10        "config flatten"
11    }
12
13    fn signature(&self) -> Signature {
14        Signature::build(self.name())
15            .category(Category::Debug)
16            .input_output_types(vec![(Type::Nothing, Type::record())])
17    }
18
19    fn description(&self) -> &str {
20        "Show the current configuration in a flattened form."
21    }
22
23    fn examples(&self) -> Vec<Example<'_>> {
24        vec![Example {
25            description: "Show the current configuration in a flattened form.",
26            example: "config flatten",
27            result: None,
28        }]
29    }
30
31    fn run(
32        &self,
33        engine_state: &EngineState,
34        stack: &mut Stack,
35        call: &Call,
36        _input: PipelineData,
37    ) -> Result<PipelineData, ShellError> {
38        // Get the Config instance from the stack
39        let config = stack.get_config(engine_state);
40        // Serialize the Config instance to JSON
41        let serialized_config = serde_json::to_value(&*config).map_err(|err| {
42            ShellError::Generic(GenericError::new(
43                format!("Failed to serialize config to json: {err}"),
44                "",
45                call.head,
46            ))
47        })?;
48        // Create a JsonFlattener instance with appropriate arguments
49        let flattener = JsonFlattener {
50            separator: ".",
51            alt_array_flattening: false,
52            preserve_arrays: true,
53        };
54        // Flatten the JSON value
55        let flattened_config_str = flattener.flatten(&serialized_config).to_string();
56        let flattened_values =
57            convert_string_to_value(&flattened_config_str, engine_state, call.head)?;
58
59        Ok(flattened_values.into_pipeline_data())
60    }
61}
62
63// From here below is taken from `from json`. Would be nice to have a nu-utils-value crate that could be shared
64fn convert_string_to_value(
65    string_input: &str,
66    engine_state: &EngineState,
67    span: Span,
68) -> Result<Value, ShellError> {
69    match nu_json::from_str(string_input) {
70        Ok(value) => Ok(convert_nujson_to_value(None, value, engine_state, span)),
71
72        Err(x) => match x {
73            nu_json::Error::Syntax(_, row, col) => {
74                let label = x.to_string();
75                let label_span = Span::from_row_column(row, col, string_input);
76                Err(ShellError::Generic(
77                    GenericError::new(
78                        "Error while parsing JSON text",
79                        "error parsing JSON text",
80                        span,
81                    )
82                    .with_inner([ShellError::OutsideSpannedLabeledError {
83                        src: string_input.into(),
84                        error: "Error while parsing JSON text".into(),
85                        msg: label,
86                        span: label_span,
87                    }]),
88                ))
89            }
90            x => Err(ShellError::CantConvert {
91                to_type: format!("structured json data ({x})"),
92                from_type: "string".into(),
93                span,
94                help: None,
95            }),
96        },
97    }
98}
99
100fn convert_nujson_to_value(
101    key: Option<String>,
102    value: nu_json::Value,
103    engine_state: &EngineState,
104    span: Span,
105) -> Value {
106    match value {
107        nu_json::Value::Array(array) => Value::list(
108            array
109                .into_iter()
110                .map(|x| convert_nujson_to_value(key.clone(), x, engine_state, span))
111                .collect(),
112            span,
113        ),
114        nu_json::Value::Bool(b) => Value::bool(b, span),
115        nu_json::Value::F64(f) => Value::float(f, span),
116        nu_json::Value::I64(i) => {
117            if let Some(closure_str) = expand_closure(key.clone(), i, engine_state) {
118                Value::string(closure_str, span)
119            } else {
120                Value::int(i, span)
121            }
122        }
123        nu_json::Value::Null => Value::nothing(span),
124        nu_json::Value::Object(k) => Value::record(
125            k.into_iter()
126                .map(|(k, v)| {
127                    let mut key = k.clone();
128                    // Keep .Closure.val and .block_id as part of the key during conversion to value
129                    let value = convert_nujson_to_value(Some(key.clone()), v, engine_state, span);
130                    // Replace .Closure.val and .block_id from the key after the conversion
131                    if key.contains(".Closure.val") || key.contains(".block_id") {
132                        key = key.replace(".Closure.val", "").replace(".block_id", "");
133                    }
134                    (key, value)
135                })
136                .collect(),
137            span,
138        ),
139        nu_json::Value::U64(u) => {
140            if u > i64::MAX as u64 {
141                Value::error(
142                    ShellError::CantConvert {
143                        to_type: "i64 sized integer".into(),
144                        from_type: "value larger than i64".into(),
145                        span,
146                        help: None,
147                    },
148                    span,
149                )
150            } else if let Some(closure_str) = expand_closure(key.clone(), u as i64, engine_state) {
151                Value::string(closure_str, span)
152            } else {
153                Value::int(u as i64, span)
154            }
155        }
156        nu_json::Value::String(s) => Value::string(s, span),
157    }
158}
159
160// If the block_id is a real block id, then it should expand into the closure contents, otherwise return None
161fn expand_closure(
162    key: Option<String>,
163    block_id: i64,
164    engine_state: &EngineState,
165) -> Option<String> {
166    match key {
167        Some(key) if key.contains(".Closure.val") || key.contains(".block_id") => engine_state
168            .try_get_block(nu_protocol::BlockId::new(block_id as usize))
169            .and_then(|block| block.span)
170            .map(|span| {
171                let contents = engine_state.get_span_contents(span);
172                String::from_utf8_lossy(contents).to_string()
173            }),
174        _ => None,
175    }
176}