From 1e456daff503be61d99531a7e33ae0d987924bdd Mon Sep 17 00:00:00 2001 From: alxndrv <> Date: Wed, 12 Feb 2025 18:36:33 +0200 Subject: [PATCH] `lscpu`: Use recursive printing logic to output nested fields --- src/uu/lscpu/src/lscpu.rs | 112 ++++++++++++++++++++++++++++---------- 1 file changed, 84 insertions(+), 28 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 = 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 = vec![]; + + if let Some(model_name) = find_cpuinfo_value(&contents, "model name") { + let mut model_children: Vec = 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)); } - if json { - println!("{}", cpu_infos.to_json()); - } else { - for elt in cpu_infos.lscpu { - println!("{}: {}", elt.field, elt.data); + 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, 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); } } - Ok(()) + + print_entries(infos.lscpu, 0, &out_opts); } fn find_cpuinfo_value(contents: &str, key: &str) -> Option { @@ -101,11 +156,11 @@ fn find_cpuinfo_value(contents: &str, key: &str) -> Option { .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 \