From 9e1b825a7db3610b1a12524b1a5b4d3a37a6a237 Mon Sep 17 00:00:00 2001
From: alxndrv <>
Date: Wed, 12 Feb 2025 13:17:28 +0200
Subject: [PATCH 1/9] `lscpu`: Add support for nested fields in output

---
 src/uu/lscpu/src/lscpu.rs | 11 +++++++----
 1 file changed, 7 insertions(+), 4 deletions(-)

diff --git a/src/uu/lscpu/src/lscpu.rs b/src/uu/lscpu/src/lscpu.rs
index 5dd313b..0bde18f 100644
--- a/src/uu/lscpu/src/lscpu.rs
+++ b/src/uu/lscpu/src/lscpu.rs
@@ -27,6 +27,8 @@ struct CpuInfos {
 struct CpuInfo {
     field: String,
     data: String,
+    #[serde(skip_serializing_if = "Vec::is_empty")]
+    children: Vec<CpuInfo>,
 }
 
 impl CpuInfos {
@@ -36,10 +38,11 @@ impl CpuInfos {
         }
     }
 
-    fn push(&mut self, field: &str, data: &str) {
+    fn push(&mut self, field: &str, data: &str, children: Option<Vec<CpuInfo>>) {
         let cpu_info = CpuInfo {
             field: String::from_str(field).unwrap(),
             data: String::from_str(data).unwrap(),
+            children: children.unwrap_or_default(),
         };
         self.lscpu.push(cpu_info);
     }
@@ -59,8 +62,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
     let json = matches.get_flag(options::JSON);
 
     let mut cpu_infos = CpuInfos::new();
-    cpu_infos.push("Architecture", &get_architecture());
-    cpu_infos.push("CPU(s)", &format!("{}", system.cpus().len()));
+    cpu_infos.push("Architecture", &get_architecture(), None);
+    cpu_infos.push("CPU(s)", &format!("{}", system.cpus().len()), None);
     // Add more CPU information here...
 
     if let Ok(contents) = fs::read_to_string("/proc/cpuinfo") {
@@ -70,7 +73,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
             .unwrap();
         // Assuming all CPUs have the same model name
         if let Some(cap) = re.captures_iter(&contents).next() {
-            cpu_infos.push("Model name", &cap[1]);
+            cpu_infos.push("Model name", &cap[1], None);
         };
     }
 

From edb3b83ec91362271b07122672cdaafeb011eef9 Mon Sep 17 00:00:00 2001
From: alxndrv <>
Date: Wed, 12 Feb 2025 17:05:37 +0200
Subject: [PATCH 2/9] `lscpu`: Use common function to read fields from
 `/proc/cpuinfo`

---
 src/uu/lscpu/src/lscpu.rs | 37 +++++++++++++++++++++++++++++--------
 1 file changed, 29 insertions(+), 8 deletions(-)

diff --git a/src/uu/lscpu/src/lscpu.rs b/src/uu/lscpu/src/lscpu.rs
index 0bde18f..2fc84cf 100644
--- a/src/uu/lscpu/src/lscpu.rs
+++ b/src/uu/lscpu/src/lscpu.rs
@@ -67,14 +67,21 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
     // Add more CPU information here...
 
     if let Ok(contents) = fs::read_to_string("/proc/cpuinfo") {
-        let re = RegexBuilder::new(r"^model name\s+:\s+(.*)$")
-            .multi_line(true)
-            .build()
-            .unwrap();
-        // Assuming all CPUs have the same model name
-        if let Some(cap) = re.captures_iter(&contents).next() {
-            cpu_infos.push("Model name", &cap[1], None);
-        };
+        if let Some(cpu_model) = find_cpuinfo_value(&contents, "model name") {
+            if let Some(addr_sizes) = find_cpuinfo_value(&contents, "address sizes") {
+                cpu_infos.push(
+                    "Model name",
+                    cpu_model.as_str(),
+                    Some(vec![CpuInfo {
+                        field: "Address sizes".to_string(),
+                        data: addr_sizes,
+                        children: vec![],
+                    }]),
+                );
+            } else {
+                cpu_infos.push("Model name", cpu_model.as_str(), None);
+            }
+        }
     }
 
     if json {
@@ -87,6 +94,20 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
     Ok(())
 }
 
+fn find_cpuinfo_value(contents: &str, key: &str) -> Option<String> {
+    let pattern = format!(r"^{}\s+:\s+(.*)$", key);
+    let re = RegexBuilder::new(pattern.as_str())
+        .multi_line(true)
+        .build()
+        .unwrap();
+
+    if let Some(cap) = re.captures_iter(contents).next() {
+        return Some(cap[1].to_string());
+    };
+
+    None
+}
+
 fn get_architecture() -> String {
     if cfg!(target_arch = "x86") {
         "x86".to_string()

From 1e456daff503be61d99531a7e33ae0d987924bdd Mon Sep 17 00:00:00 2001
From: alxndrv <>
Date: Wed, 12 Feb 2025 18:36:33 +0200
Subject: [PATCH 3/9] `lscpu`: Use recursive printing logic to output nested
 fields

---
 src/uu/lscpu/src/lscpu.rs | 116 ++++++++++++++++++++++++++++----------
 1 file changed, 86 insertions(+), 30 deletions(-)

diff --git a/src/uu/lscpu/src/lscpu.rs b/src/uu/lscpu/src/lscpu.rs
index 2fc84cf..21cb5c6 100644
--- a/src/uu/lscpu/src/lscpu.rs
+++ b/src/uu/lscpu/src/lscpu.rs
@@ -52,46 +52,101 @@ impl CpuInfos {
     }
 }
 
+struct OutputOptions {
+    json: bool,
+    _hex: bool,
+}
+
 #[uucore::main]
 pub fn uumain(args: impl uucore::Args) -> UResult<()> {
     let matches: clap::ArgMatches = uu_app().try_get_matches_from(args)?;
 
     let system = System::new_all();
 
-    let _hex = matches.get_flag(options::HEX);
-    let json = matches.get_flag(options::JSON);
+    let output_opts = OutputOptions {
+        _hex: matches.get_flag(options::HEX),
+        json: matches.get_flag(options::JSON),
+    };
 
     let mut cpu_infos = CpuInfos::new();
-    cpu_infos.push("Architecture", &get_architecture(), None);
     cpu_infos.push("CPU(s)", &format!("{}", system.cpus().len()), None);
     // Add more CPU information here...
 
-    if let Ok(contents) = fs::read_to_string("/proc/cpuinfo") {
-        if let Some(cpu_model) = find_cpuinfo_value(&contents, "model name") {
-            if let Some(addr_sizes) = find_cpuinfo_value(&contents, "address sizes") {
-                cpu_infos.push(
-                    "Model name",
-                    cpu_model.as_str(),
-                    Some(vec![CpuInfo {
-                        field: "Address sizes".to_string(),
-                        data: addr_sizes,
-                        children: vec![],
-                    }]),
-                );
-            } else {
-                cpu_infos.push("Model name", cpu_model.as_str(), None);
+    let contents = match fs::read_to_string("/proc/cpuinfo") {
+        // Early return if we can't read /proc/cpuinfo for whatever reason
+        // TODO: Should this return an non-zero exit code to user, or do we just ignore it and display whatever CPU info we could find?
+        Err(_) => return Ok(()),
+        Ok(contents) => contents,
+    };
+
+    let mut arch_children: Vec<CpuInfo> = vec![];
+
+    if let Some(addr_sizes) = find_cpuinfo_value(&contents, "address sizes") {
+        arch_children.push(CpuInfo {
+            field: "Address sizes".to_string(),
+            data: addr_sizes,
+            children: vec![],
+        })
+    }
+
+    cpu_infos.push("Architecture", &get_architecture(), Some(arch_children));
+
+    // TODO: This is currently quite verbose and doesn't strictly respect the hierarchy of `/proc/cpuinfo` contents
+    // ie. the file might contain multiple sections, each with their own vendor_id/model name etc. but right now
+    // we're just taking whatever our regex matches first and using that
+    if let Some(vendor) = find_cpuinfo_value(&contents, "vendor_id") {
+        let mut vendor_children: Vec<CpuInfo> = vec![];
+
+        if let Some(model_name) = find_cpuinfo_value(&contents, "model name") {
+            let mut model_children: Vec<CpuInfo> = vec![];
+
+            if let Some(family) = find_cpuinfo_value(&contents, "cpu family") {
+                model_children.push(CpuInfo {
+                    field: "CPU Family".to_string(),
+                    data: family,
+                    children: vec![],
+                });
             }
+
+            if let Some(model) = find_cpuinfo_value(&contents, "model") {
+                model_children.push(CpuInfo {
+                    field: "Model".to_string(),
+                    data: model,
+                    children: vec![],
+                });
+            }
+
+            vendor_children.push(CpuInfo {
+                field: "Model name".to_string(),
+                data: model_name,
+                children: model_children,
+            });
+        }
+        cpu_infos.push("Vendor ID", vendor.as_str(), Some(vendor_children));
+    }
+
+    print_output(cpu_infos, output_opts);
+
+    Ok(())
+}
+
+fn print_output(infos: CpuInfos, out_opts: OutputOptions) {
+    if out_opts.json {
+        println!("{}", infos.to_json());
+        return;
+    }
+
+    // Recursive function to print nested CpuInfo entries
+    fn print_entries(entries: Vec<CpuInfo>, depth: usize, _out_opts: &OutputOptions) {
+        let indent = "  ".repeat(depth);
+        for entry in entries {
+            // TODO: Align `data` values to form a column
+            println!("{}{}: {}", indent, entry.field, entry.data);
+            print_entries(entry.children, depth + 1, _out_opts);
         }
     }
 
-    if json {
-        println!("{}", cpu_infos.to_json());
-    } else {
-        for elt in cpu_infos.lscpu {
-            println!("{}: {}", elt.field, elt.data);
-        }
-    }
-    Ok(())
+    print_entries(infos.lscpu, 0, &out_opts);
 }
 
 fn find_cpuinfo_value(contents: &str, key: &str) -> Option<String> {
@@ -101,11 +156,11 @@ fn find_cpuinfo_value(contents: &str, key: &str) -> Option<String> {
         .build()
         .unwrap();
 
-    if let Some(cap) = re.captures_iter(contents).next() {
-        return Some(cap[1].to_string());
-    };
-
-    None
+    let value = re
+        .captures_iter(contents)
+        .next()
+        .map(|cap| cap[1].to_string());
+    value
 }
 
 fn get_architecture() -> String {
@@ -137,6 +192,7 @@ pub fn uu_app() -> Command {
         )
         .arg(
             Arg::new(options::JSON)
+                .short('J')
                 .long("json")
                 .help(
                     "Use JSON output format for the default summary or extended output \

From 3953552e3aa145a2d0770af1afabc31e810a4a82 Mon Sep 17 00:00:00 2001
From: alxndrv <>
Date: Wed, 12 Feb 2025 18:56:34 +0200
Subject: [PATCH 4/9] `lscpu`: Clean up creation of new entries

---
 src/uu/lscpu/src/lscpu.rs | 66 ++++++++++++++++++---------------------
 1 file changed, 31 insertions(+), 35 deletions(-)

diff --git a/src/uu/lscpu/src/lscpu.rs b/src/uu/lscpu/src/lscpu.rs
index 21cb5c6..ad1acc1 100644
--- a/src/uu/lscpu/src/lscpu.rs
+++ b/src/uu/lscpu/src/lscpu.rs
@@ -6,7 +6,7 @@
 use clap::{crate_version, Arg, ArgAction, Command};
 use regex::RegexBuilder;
 use serde::Serialize;
-use std::{fs, str::FromStr};
+use std::fs;
 use sysinfo::System;
 use uucore::{error::UResult, format_usage, help_about, help_usage};
 
@@ -31,6 +31,20 @@ struct CpuInfo {
     children: Vec<CpuInfo>,
 }
 
+impl CpuInfo {
+    fn new(field: &str, data: &str, children: Option<Vec<CpuInfo>>) -> Self {
+        Self {
+            field: field.to_string(),
+            data: data.to_string(),
+            children: children.unwrap_or_default(),
+        }
+    }
+
+    fn add_child(&mut self, child: Self) {
+        self.children.push(child);
+    }
+}
+
 impl CpuInfos {
     fn new() -> CpuInfos {
         CpuInfos {
@@ -38,12 +52,7 @@ impl CpuInfos {
         }
     }
 
-    fn push(&mut self, field: &str, data: &str, children: Option<Vec<CpuInfo>>) {
-        let cpu_info = CpuInfo {
-            field: String::from_str(field).unwrap(),
-            data: String::from_str(data).unwrap(),
-            children: children.unwrap_or_default(),
-        };
+    fn push(&mut self, cpu_info: CpuInfo) {
         self.lscpu.push(cpu_info);
     }
 
@@ -69,8 +78,13 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
     };
 
     let mut cpu_infos = CpuInfos::new();
-    cpu_infos.push("CPU(s)", &format!("{}", system.cpus().len()), None);
-    // Add more CPU information here...
+    cpu_infos.push(CpuInfo::new(
+        "CPU(s)",
+        &format!("{}", system.cpus().len()),
+        None,
+    ));
+
+    let mut arch_info = CpuInfo::new("Architecture", &get_architecture(), None);
 
     let contents = match fs::read_to_string("/proc/cpuinfo") {
         // Early return if we can't read /proc/cpuinfo for whatever reason
@@ -79,50 +93,32 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
         Ok(contents) => contents,
     };
 
-    let mut arch_children: Vec<CpuInfo> = vec![];
-
     if let Some(addr_sizes) = find_cpuinfo_value(&contents, "address sizes") {
-        arch_children.push(CpuInfo {
-            field: "Address sizes".to_string(),
-            data: addr_sizes,
-            children: vec![],
-        })
+        arch_info.add_child(CpuInfo::new("Address sizes", &addr_sizes, None))
     }
 
-    cpu_infos.push("Architecture", &get_architecture(), Some(arch_children));
+    cpu_infos.push(arch_info);
 
     // TODO: This is currently quite verbose and doesn't strictly respect the hierarchy of `/proc/cpuinfo` contents
     // ie. the file might contain multiple sections, each with their own vendor_id/model name etc. but right now
     // we're just taking whatever our regex matches first and using that
     if let Some(vendor) = find_cpuinfo_value(&contents, "vendor_id") {
-        let mut vendor_children: Vec<CpuInfo> = vec![];
+        let mut vendor_info = CpuInfo::new("Vendor ID", &vendor, None);
 
         if let Some(model_name) = find_cpuinfo_value(&contents, "model name") {
-            let mut model_children: Vec<CpuInfo> = vec![];
+            let mut model_name_info = CpuInfo::new("Model name", &model_name, None);
 
             if let Some(family) = find_cpuinfo_value(&contents, "cpu family") {
-                model_children.push(CpuInfo {
-                    field: "CPU Family".to_string(),
-                    data: family,
-                    children: vec![],
-                });
+                model_name_info.add_child(CpuInfo::new("CPU Family", &family, None));
             }
 
             if let Some(model) = find_cpuinfo_value(&contents, "model") {
-                model_children.push(CpuInfo {
-                    field: "Model".to_string(),
-                    data: model,
-                    children: vec![],
-                });
+                model_name_info.add_child(CpuInfo::new("Model", &model, None));
             }
 
-            vendor_children.push(CpuInfo {
-                field: "Model name".to_string(),
-                data: model_name,
-                children: model_children,
-            });
+            vendor_info.add_child(model_name_info);
         }
-        cpu_infos.push("Vendor ID", vendor.as_str(), Some(vendor_children));
+        cpu_infos.push(vendor_info);
     }
 
     print_output(cpu_infos, output_opts);

From 5e98c668823a24cd97916a7f5745dc013787a5bb Mon Sep 17 00:00:00 2001
From: alxndrv <>
Date: Wed, 12 Feb 2025 19:15:57 +0200
Subject: [PATCH 5/9] `lscpu`: Don't early-return on failure to read
 `/proc/cpuinfo`

---
 src/uu/lscpu/src/lscpu.rs | 9 +++------
 1 file changed, 3 insertions(+), 6 deletions(-)

diff --git a/src/uu/lscpu/src/lscpu.rs b/src/uu/lscpu/src/lscpu.rs
index ad1acc1..bdeb776 100644
--- a/src/uu/lscpu/src/lscpu.rs
+++ b/src/uu/lscpu/src/lscpu.rs
@@ -86,12 +86,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
 
     let mut arch_info = CpuInfo::new("Architecture", &get_architecture(), None);
 
-    let contents = match fs::read_to_string("/proc/cpuinfo") {
-        // Early return if we can't read /proc/cpuinfo for whatever reason
-        // TODO: Should this return an non-zero exit code to user, or do we just ignore it and display whatever CPU info we could find?
-        Err(_) => return Ok(()),
-        Ok(contents) => contents,
-    };
+    // TODO: We just silently ignore failures to read `/proc/cpuinfo` currently and treat it as empty
+    // Perhaps a better solution should be put in place, but what?
+    let contents = fs::read_to_string("/proc/cpuinfo").unwrap_or_default();
 
     if let Some(addr_sizes) = find_cpuinfo_value(&contents, "address sizes") {
         arch_info.add_child(CpuInfo::new("Address sizes", &addr_sizes, None))

From e0a6ec7eb5e1cfe1c2430ed4f3b6cf56a0ac0fdb Mon Sep 17 00:00:00 2001
From: alxndrv <>
Date: Wed, 12 Feb 2025 19:40:34 +0200
Subject: [PATCH 6/9] `lscpu`: Add support for CPU byte order

---
 src/uu/lscpu/src/lscpu.rs | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/src/uu/lscpu/src/lscpu.rs b/src/uu/lscpu/src/lscpu.rs
index bdeb776..7b12734 100644
--- a/src/uu/lscpu/src/lscpu.rs
+++ b/src/uu/lscpu/src/lscpu.rs
@@ -94,6 +94,15 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
         arch_info.add_child(CpuInfo::new("Address sizes", &addr_sizes, None))
     }
 
+    if let Ok(byte_order) = fs::read_to_string("/sys/kernel/cpu_byteorder") {
+        match byte_order.trim() {
+            "big" => arch_info.add_child(CpuInfo::new("Byte Order", "Big Endian", None)),
+            "little" => arch_info.add_child(CpuInfo::new("Byte Order", "Little Endian", None)),
+            _ => eprintln!("Unrecognised Byte Order: {}", byte_order)
+
+        }
+    }
+
     cpu_infos.push(arch_info);
 
     // TODO: This is currently quite verbose and doesn't strictly respect the hierarchy of `/proc/cpuinfo` contents

From 0281da00dd6d752416728a70de85282f99aab9fb Mon Sep 17 00:00:00 2001
From: alxndrv <>
Date: Wed, 12 Feb 2025 20:22:05 +0200
Subject: [PATCH 7/9] `lscpu`: Align output values to the same column

---
 src/uu/lscpu/src/lscpu.rs | 55 ++++++++++++++++++++++++++++++++-------
 1 file changed, 45 insertions(+), 10 deletions(-)

diff --git a/src/uu/lscpu/src/lscpu.rs b/src/uu/lscpu/src/lscpu.rs
index 7b12734..0676a1d 100644
--- a/src/uu/lscpu/src/lscpu.rs
+++ b/src/uu/lscpu/src/lscpu.rs
@@ -6,7 +6,7 @@
 use clap::{crate_version, Arg, ArgAction, Command};
 use regex::RegexBuilder;
 use serde::Serialize;
-use std::fs;
+use std::{cmp, fs};
 use sysinfo::System;
 use uucore::{error::UResult, format_usage, help_about, help_usage};
 
@@ -98,8 +98,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
         match byte_order.trim() {
             "big" => arch_info.add_child(CpuInfo::new("Byte Order", "Big Endian", None)),
             "little" => arch_info.add_child(CpuInfo::new("Byte Order", "Little Endian", None)),
-            _ => eprintln!("Unrecognised Byte Order: {}", byte_order)
-
+            _ => eprintln!("Unrecognised Byte Order: {}", byte_order),
         }
     }
 
@@ -138,17 +137,53 @@ fn print_output(infos: CpuInfos, out_opts: OutputOptions) {
         return;
     }
 
-    // Recursive function to print nested CpuInfo entries
-    fn print_entries(entries: Vec<CpuInfo>, depth: usize, _out_opts: &OutputOptions) {
-        let indent = "  ".repeat(depth);
+    fn indentation(depth: usize) -> usize {
+        // Indentation is 2 spaces per level, used in a few places, hence its own helper function
+        depth * 2
+    }
+
+    // Recurses down the tree of entries and find the one with the "widest" field name (taking into account tree depth)
+    fn get_max_field_width(info: &CpuInfo, depth: usize) -> usize {
+        let max_child_width = info
+            .children
+            .iter()
+            .map(|entry| get_max_field_width(entry, depth + 1))
+            .max()
+            .unwrap_or_default();
+
+        let own_width = indentation(depth) + info.field.len();
+        cmp::max(own_width, max_child_width)
+    }
+
+    // Used later to align all values to the same column
+    let max_field_width = infos
+        .lscpu
+        .iter()
+        .map(|info| get_max_field_width(info, 0))
+        .max()
+        .unwrap();
+
+    fn print_entries(
+        entries: &Vec<CpuInfo>,
+        depth: usize,
+        max_field_width: usize,
+        _out_opts: &OutputOptions,
+    ) {
         for entry in entries {
-            // TODO: Align `data` values to form a column
-            println!("{}{}: {}", indent, entry.field, entry.data);
-            print_entries(entry.children, depth + 1, _out_opts);
+            let margin = indentation(depth);
+            let padding = cmp::max(max_field_width - margin - entry.field.len(), 0);
+            println!(
+                "{}{}:{} {}",
+                " ".repeat(margin),
+                entry.field,
+                " ".repeat(padding),
+                entry.data
+            );
+            print_entries(&entry.children, depth + 1, max_field_width, _out_opts);
         }
     }
 
-    print_entries(infos.lscpu, 0, &out_opts);
+    print_entries(&infos.lscpu, 0, max_field_width, &out_opts);
 }
 
 fn find_cpuinfo_value(contents: &str, key: &str) -> Option<String> {

From 21706eb103ab8585d8c15769bcd8d60ad1534a54 Mon Sep 17 00:00:00 2001
From: alxndrv <>
Date: Thu, 13 Feb 2025 12:28:00 +0200
Subject: [PATCH 8/9] `lscpu`: Move code around as per the review comments

---
 src/uu/lscpu/src/lscpu.rs | 26 +++++++++++++-------------
 1 file changed, 13 insertions(+), 13 deletions(-)

diff --git a/src/uu/lscpu/src/lscpu.rs b/src/uu/lscpu/src/lscpu.rs
index 0676a1d..3150e53 100644
--- a/src/uu/lscpu/src/lscpu.rs
+++ b/src/uu/lscpu/src/lscpu.rs
@@ -78,11 +78,6 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
     };
 
     let mut cpu_infos = CpuInfos::new();
-    cpu_infos.push(CpuInfo::new(
-        "CPU(s)",
-        &format!("{}", system.cpus().len()),
-        None,
-    ));
 
     let mut arch_info = CpuInfo::new("Architecture", &get_architecture(), None);
 
@@ -103,6 +98,11 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
     }
 
     cpu_infos.push(arch_info);
+    cpu_infos.push(CpuInfo::new(
+        "CPU(s)",
+        &format!("{}", system.cpus().len()),
+        None,
+    ));
 
     // TODO: This is currently quite verbose and doesn't strictly respect the hierarchy of `/proc/cpuinfo` contents
     // ie. the file might contain multiple sections, each with their own vendor_id/model name etc. but right now
@@ -155,14 +155,6 @@ fn print_output(infos: CpuInfos, out_opts: OutputOptions) {
         cmp::max(own_width, max_child_width)
     }
 
-    // Used later to align all values to the same column
-    let max_field_width = infos
-        .lscpu
-        .iter()
-        .map(|info| get_max_field_width(info, 0))
-        .max()
-        .unwrap();
-
     fn print_entries(
         entries: &Vec<CpuInfo>,
         depth: usize,
@@ -183,6 +175,14 @@ fn print_output(infos: CpuInfos, out_opts: OutputOptions) {
         }
     }
 
+    // Used to align all values to the same column
+    let max_field_width = infos
+        .lscpu
+        .iter()
+        .map(|info| get_max_field_width(info, 0))
+        .max()
+        .unwrap();
+
     print_entries(&infos.lscpu, 0, max_field_width, &out_opts);
 }
 

From 95d57eb23064b15bb1787e61f83b4c78b1183b79 Mon Sep 17 00:00:00 2001
From: alxndrv <>
Date: Thu, 13 Feb 2025 12:48:30 +0200
Subject: [PATCH 9/9] `lscpu`: Improve tests a tiny bit

---
 tests/by-util/test_lscpu.rs | 33 +++++++++++++++++++++++++++------
 1 file changed, 27 insertions(+), 6 deletions(-)

diff --git a/tests/by-util/test_lscpu.rs b/tests/by-util/test_lscpu.rs
index ca4c92e..474001d 100644
--- a/tests/by-util/test_lscpu.rs
+++ b/tests/by-util/test_lscpu.rs
@@ -17,12 +17,33 @@ fn test_hex() {
 }
 
 #[test]
+#[cfg(target_os = "linux")]
 fn test_json() {
-    new_ucmd!()
-        .arg("--json")
-        .succeeds()
-        // ensure some fields are there, non-exhausting
-        .stdout_contains("\"lscpu\": [")
+    let res = new_ucmd!().arg("--json").succeeds();
+
+    let stdout = res.no_stderr().stdout_str();
+    assert!(stdout.starts_with("{"));
+    assert!(stdout.ends_with("}\n"));
+
+    res.stdout_contains("\"lscpu\": [")
         .stdout_contains("\"field\": \"Architecture\"")
-        .stdout_contains("\"field\": \"CPU(s)\"");
+        .stdout_contains("\"field\": \"CPU(s)\"")
+        .stdout_contains("\"children\": [");
+}
+
+#[test]
+#[cfg(target_os = "linux")]
+fn test_output() {
+    let res = new_ucmd!().succeeds();
+    let stdout = res.no_stderr().stdout_str();
+
+    // Non-exhaustive list of fields we expect
+    // This also checks that fields which should be indented, are indeed indented as excepted
+    assert!(stdout.contains("Architecture:"));
+    assert!(stdout.contains("\n  Address sizes:"));
+    assert!(stdout.contains("\n  Byte Order:"));
+    assert!(stdout.contains("\nCPU(s):"));
+    assert!(stdout.contains("\nVendor ID:"));
+    assert!(stdout.contains("\n  Model name:"));
+    assert!(stdout.contains("\n    CPU Family:"));
 }