nu_command/env/config/
config_flatten.rs

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