1use nu_engine::command_prelude::*;
2use nu_protocol::shell_error::generic::GenericError;
3use nu_utils::JsonFlattener; #[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 let config = stack.get_config(engine_state);
40 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 let flattener = JsonFlattener {
50 separator: ".",
51 alt_array_flattening: false,
52 preserve_arrays: true,
53 };
54 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
63fn 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 let value = convert_nujson_to_value(Some(key.clone()), v, engine_state, span);
130 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
160fn 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}