mirror of
https://github.com/nushell/nushell.git
synced 2025-08-09 17:25:15 +02:00
add config flatten
command (#14621)
# Description This is supposed to be a Quality-of-Life command that just makes some things easier when dealing with a nushell config. Really all it does is show you the current config in a flattened state. That's it. I was thinking this could be useful when comparing config settings between old and new config files. There are still room for improvements. For instance, closures are listed as an int. They can be updated with a `view source <int>` pipeline but that could all be built in too.  The command works by getting the current configuration, serializing it to json, then flattening that json. BTW, there's a new flatten_json.rs in nu-utils. Theoretically all this mess could be done in a custom command script, but it's proven to be exceedingly difficult based on the work from discord. Here's some more complex items to flatten.  # User-Facing Changes <!-- List of all changes that impact the user experience here. This helps us keep track of breaking changes. --> # Tests + Formatting <!-- Don't forget to add tests that cover your changes. Make sure you've run and fixed any issues with these commands: - `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes) - `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to check that you're using the standard code style - `cargo test --workspace` to check that all tests pass (on Windows make sure to [enable developer mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging)) - `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the tests for the standard library > **Note** > from `nushell` you can also use the `toolkit` as follows > ```bash > use toolkit.nu # or use an `env_change` hook to activate it automatically > toolkit check pr > ``` --> # After Submitting <!-- If your PR had any user-facing changes, update [the documentation](https://github.com/nushell/nushell.github.io) after the PR is merged, if necessary. This will help us keep the docs up to date. -->
This commit is contained in:
@ -24,6 +24,7 @@ log = { workspace = true }
|
||||
num-format = { workspace = true }
|
||||
strip-ansi-escapes = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
sys-locale = "0.3"
|
||||
unicase = "2.8.0"
|
||||
|
||||
@ -38,4 +39,4 @@ crossterm_winapi = "0.9"
|
||||
nix = { workspace = true, default-features = false, features = ["user", "fs"] }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
workspace = true
|
||||
|
247
crates/nu-utils/src/flatten_json.rs
Normal file
247
crates/nu-utils/src/flatten_json.rs
Normal file
@ -0,0 +1,247 @@
|
||||
use serde_json::{json, Map, Value as SerdeValue};
|
||||
|
||||
/// JsonFlattener is the main driver when flattening JSON
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use nu_utils;
|
||||
///
|
||||
/// let flattener = nu_utils::JsonFlattener { ..Default::default() };
|
||||
/// ```
|
||||
pub struct JsonFlattener<'a> {
|
||||
/// Alternate separator used between keys when flattening
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use nu_utils;
|
||||
/// let flattener = nu_utils::JsonFlattener { separator: "_", ..Default::default()};
|
||||
/// ```
|
||||
pub separator: &'a str,
|
||||
/// Opinionated flattening format that places values in an array if the object is nested inside an array
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use nu_utils;
|
||||
/// let flattener = nu_utils::JsonFlattener { alt_array_flattening: true, ..Default::default()};
|
||||
/// ```
|
||||
pub alt_array_flattening: bool,
|
||||
/// Completely flatten JSON and keep array structure in the key when flattening
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use nu_utils;
|
||||
/// let flattener = nu_utils::JsonFlattener { preserve_arrays: true, ..Default::default()};
|
||||
/// ```
|
||||
pub preserve_arrays: bool,
|
||||
}
|
||||
|
||||
impl<'a> Default for JsonFlattener<'a> {
|
||||
fn default() -> Self {
|
||||
JsonFlattener {
|
||||
separator: ".",
|
||||
alt_array_flattening: false,
|
||||
preserve_arrays: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This implementation defines the core usage for the `JsonFlattener` structure.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use nu_utils;
|
||||
/// use serde_json::json;
|
||||
///
|
||||
/// let flattener = nu_utils::JsonFlattener::new();
|
||||
/// let example = json!({
|
||||
/// "a": {
|
||||
/// "b": "c"
|
||||
/// }
|
||||
/// });
|
||||
///
|
||||
/// let flattened_example = flattener.flatten(&example);
|
||||
/// ```
|
||||
impl<'a> JsonFlattener<'a> {
|
||||
/// Returns a flattener with the default arguments
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use nu_utils;
|
||||
///
|
||||
/// let flattener = nu_utils::JsonFlattener::new();
|
||||
/// ```
|
||||
pub fn new() -> Self {
|
||||
JsonFlattener {
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Flattens JSON variants into a JSON object
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `json` - A serde_json Value to flatten
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use nu_utils;
|
||||
/// use serde_json::json;
|
||||
///
|
||||
/// let flattener = nu_utils::JsonFlattener::new();
|
||||
/// let example = json!({
|
||||
/// "name": "John Doe",
|
||||
/// "age": 43,
|
||||
/// "address": {
|
||||
/// "street": "10 Downing Street",
|
||||
/// "city": "London"
|
||||
/// },
|
||||
/// "phones": [
|
||||
/// "+44 1234567",
|
||||
/// "+44 2345678"
|
||||
/// ]
|
||||
/// });
|
||||
///
|
||||
/// let flattened_example = flattener.flatten(&example);
|
||||
/// ```
|
||||
pub fn flatten(&self, json: &SerdeValue) -> SerdeValue {
|
||||
let mut flattened_val = Map::<String, SerdeValue>::new();
|
||||
match json {
|
||||
SerdeValue::Array(obj_arr) => {
|
||||
self.flatten_array(&mut flattened_val, &"".to_string(), obj_arr)
|
||||
}
|
||||
SerdeValue::Object(obj_val) => {
|
||||
self.flatten_object(&mut flattened_val, None, obj_val, false)
|
||||
}
|
||||
_ => self.flatten_value(&mut flattened_val, &"".to_string(), json, false),
|
||||
}
|
||||
SerdeValue::Object(flattened_val)
|
||||
}
|
||||
|
||||
fn flatten_object(
|
||||
&self,
|
||||
builder: &mut Map<String, SerdeValue>,
|
||||
identifier: Option<&String>,
|
||||
obj: &Map<String, SerdeValue>,
|
||||
arr: bool,
|
||||
) {
|
||||
for (k, v) in obj {
|
||||
let expanded_identifier = identifier.map_or_else(
|
||||
|| k.clone(),
|
||||
|identifier| format!("{identifier}{}{k}", self.separator),
|
||||
);
|
||||
|
||||
if expanded_identifier.contains("span.start")
|
||||
|| expanded_identifier.contains("span.end")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let expanded_identifier = self.filter_known_keys(&expanded_identifier);
|
||||
|
||||
match v {
|
||||
SerdeValue::Object(obj_val) => {
|
||||
self.flatten_object(builder, Some(&expanded_identifier), obj_val, arr)
|
||||
}
|
||||
SerdeValue::Array(obj_arr) => {
|
||||
self.flatten_array(builder, &expanded_identifier, obj_arr)
|
||||
}
|
||||
_ => self.flatten_value(builder, &expanded_identifier, v, arr),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn flatten_array(
|
||||
&self,
|
||||
builder: &mut Map<String, SerdeValue>,
|
||||
identifier: &String,
|
||||
obj: &[SerdeValue],
|
||||
) {
|
||||
for (k, v) in obj.iter().enumerate() {
|
||||
let with_key = format!("{identifier}{}{k}", self.separator);
|
||||
if with_key.contains("span.start") || with_key.contains("span.end") {
|
||||
continue;
|
||||
}
|
||||
|
||||
let with_key = self.filter_known_keys(&with_key);
|
||||
|
||||
match v {
|
||||
SerdeValue::Object(obj_val) => self.flatten_object(
|
||||
builder,
|
||||
Some(if self.preserve_arrays {
|
||||
&with_key
|
||||
} else {
|
||||
identifier
|
||||
}),
|
||||
obj_val,
|
||||
self.alt_array_flattening,
|
||||
),
|
||||
SerdeValue::Array(obj_arr) => self.flatten_array(
|
||||
builder,
|
||||
if self.preserve_arrays {
|
||||
&with_key
|
||||
} else {
|
||||
identifier
|
||||
},
|
||||
obj_arr,
|
||||
),
|
||||
_ => self.flatten_value(
|
||||
builder,
|
||||
if self.preserve_arrays {
|
||||
&with_key
|
||||
} else {
|
||||
identifier
|
||||
},
|
||||
v,
|
||||
self.alt_array_flattening,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn flatten_value(
|
||||
&self,
|
||||
builder: &mut Map<String, SerdeValue>,
|
||||
identifier: &String,
|
||||
obj: &SerdeValue,
|
||||
arr: bool,
|
||||
) {
|
||||
if let Some(v) = builder.get_mut(identifier) {
|
||||
if let Some(arr) = v.as_array_mut() {
|
||||
arr.push(obj.clone());
|
||||
} else {
|
||||
let new_val = json!(vec![v, obj]);
|
||||
builder.remove(identifier);
|
||||
builder.insert(identifier.to_string(), new_val);
|
||||
}
|
||||
} else {
|
||||
builder.insert(
|
||||
identifier.to_string(),
|
||||
if arr {
|
||||
json!(vec![obj.clone()])
|
||||
} else {
|
||||
obj.clone()
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn filter_known_keys(&self, key: &str) -> String {
|
||||
let mut filtered_key = key.to_string();
|
||||
if filtered_key.contains(".String.val") {
|
||||
filtered_key = filtered_key.replace(".String.val", "");
|
||||
}
|
||||
if filtered_key.contains(".Record.val") {
|
||||
filtered_key = filtered_key.replace(".Record.val", "");
|
||||
}
|
||||
if filtered_key.contains(".Closure.val") {
|
||||
filtered_key = filtered_key.replace(".Closure.val", "");
|
||||
}
|
||||
if filtered_key.contains(".List.vals") {
|
||||
filtered_key = filtered_key.replace(".List.vals", "");
|
||||
}
|
||||
if filtered_key.contains(".Int.val") {
|
||||
filtered_key = filtered_key.replace(".Int.val", "");
|
||||
}
|
||||
if filtered_key.contains(".Bool.val") {
|
||||
filtered_key = filtered_key.replace(".Bool.val", "");
|
||||
}
|
||||
if filtered_key.contains(".block_id") {
|
||||
filtered_key = filtered_key.replace(".block_id", "");
|
||||
}
|
||||
filtered_key
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@ mod casing;
|
||||
mod deansi;
|
||||
pub mod emoji;
|
||||
pub mod filesystem;
|
||||
pub mod flatten_json;
|
||||
pub mod locale;
|
||||
mod quoting;
|
||||
mod shared_cow;
|
||||
@ -20,6 +21,7 @@ pub use deansi::{
|
||||
strip_ansi_likely, strip_ansi_string_likely, strip_ansi_string_unlikely, strip_ansi_unlikely,
|
||||
};
|
||||
pub use emoji::contains_emoji;
|
||||
pub use flatten_json::JsonFlattener;
|
||||
pub use quoting::{escape_quote_string, needs_quoting};
|
||||
pub use shared_cow::SharedCow;
|
||||
|
||||
|
Reference in New Issue
Block a user