Skip to content

Commit

Permalink
format and legacy args for lsusb
Browse files Browse the repository at this point in the history
  • Loading branch information
tuna-f1sh committed Nov 12, 2022
1 parent e1f2323 commit 9f6fd84
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 85 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
on: [push]
on: [push, pull_request]

name: build

Expand Down
88 changes: 46 additions & 42 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,56 +1,60 @@
use std::env;
use clap::Parser;
use std::env;

mod system_profiler;

#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
/// Attempt to maintain compatibility with lsusb output
#[arg(short, long, default_value_t = false)]
lsusb: bool,
/// Attempt to maintain compatibility with lsusb output
#[arg(short, long, default_value_t = false)]
lsusb: bool,

/// Disable coloured output, can also use NO_COLOR environment variable
#[arg(short, long, default_value_t = false)]
no_colour: bool,
/// Disable coloured output, can also use NO_COLOR environment variable
#[arg(short, long, default_value_t = false)]
no_colour: bool,

/// Dump the physical USB device hierarchy as a tree
#[arg(short, long, default_value_t = false)]
tree: bool,
/// Classic dump the physical USB device hierarchy as a tree
#[arg(short = 't', long, default_value_t = false)]
lsusb_tree: bool,

/// Show only devices with the specified vendor and product ID numbers (in hexadecimal) in format VID:[PID]
#[arg(short, long)]
device: Option<String>,
/// Modern dump the physical USB device hierarchy as a tree
#[arg(short = 'T', long, default_value_t = true)]
tree: bool,

/// Show only devices with specified device and/or bus numbers (in decimal) in format [[bus]:][devnum]
#[arg(short, long)]
show: Option<String>,
/// Show only devices with the specified vendor and product ID numbers (in hexadecimal) in format VID:[PID]
#[arg(short, long)]
device: Option<String>,

/// Increase verbosity (show descriptors)
#[arg(short, long, default_value_t = false)]
verbose: bool,
}
/// Show only devices with specified device and/or bus numbers (in decimal) in format [[bus]:][devnum]
#[arg(short, long)]
show: Option<String>,

mod system_profiler;
/// Increase verbosity (show descriptors)
#[arg(short, long, default_value_t = false)]
verbose: bool,
}

fn main() {
let args = Args::parse();
let sp_usb = system_profiler::get_spusb().unwrap();

// just set the env for this process
if args.no_colour {
env::set_var("NO_COLOR", "1");
}

if args.lsusb {
if args.tree {
print!("{:+}", sp_usb);
} else {
print!("{:}", sp_usb);
}
} else {
if args.tree {
print!("{:+#}", sp_usb);
} else {
print!("{:#}", sp_usb);
}
}
let args = Args::parse();
let sp_usb = system_profiler::get_spusb().unwrap();

// just set the env for this process
if args.no_colour {
env::set_var("NO_COLOR", "1");
}

if args.lsusb {
if args.lsusb_tree {
print!("{:+}", sp_usb);
} else {
print!("{:}", sp_usb);
}
} else {
if args.tree {
print!("{:+#}", sp_usb);
} else {
print!("{:#}", sp_usb);
}
}
}
110 changes: 68 additions & 42 deletions src/system_profiler.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
use std::fmt;
/// Parser for macOS `system_profiler` command -json output with SPUSBDataType.
///
/// USBBus and USBDevice structs are used as deserializers for serde. The JSON output with the -json flag is not really JSON; all values are String regardless of contained data so it requires some extra work. Additionally, some values differ slightly from the non json output such as the speed - it is a description rather than numerical.
///
/// J.Whittington - 2022
use std::io;
use std::fmt;
use std::str::FromStr;

use colored::*;
use std::process::Command;
use serde::{Deserialize, Deserializer, Serialize};
use std::process::Command;

/// borrowed from https://github.com/vityafx/serde-aux/blob/master/src/field_attributes.rs with addition of base16 encoding
/// Deserializes an option number from string or a number.
fn deserialize_option_number_from_string<'de, T, D>(
deserializer: D,
) -> Result<Option<T>, D::Error>
fn deserialize_option_number_from_string<'de, T, D>(deserializer: D) -> Result<Option<T>, D::Error>
where
D: Deserializer<'de>,
T: FromStr + serde::Deserialize<'de>,
Expand Down Expand Up @@ -45,11 +43,13 @@ where
let base16_num = u64::from_str_radix(removed_0x.trim(), 16);
let result = match base16_num {
Ok(num) => T::from_str(num.to_string().as_str()),
Err(e) => return Err(serde::de::Error::custom(e))
Err(e) => return Err(serde::de::Error::custom(e)),
};
result.map(Some).map_err(serde::de::Error::custom)
} else {
T::from_str(s.trim()).map(Some).map_err(serde::de::Error::custom)
T::from_str(s.trim())
.map(Some)
.map_err(serde::de::Error::custom)
}
}
},
Expand All @@ -61,7 +61,7 @@ where
#[derive(Debug, Serialize, Deserialize)]
pub struct SPUSBDataType {
#[serde(rename(deserialize = "SPUSBDataType"))]
buses: Vec<USBBus>
buses: Vec<USBBus>,
}

impl fmt::Display for SPUSBDataType {
Expand Down Expand Up @@ -98,7 +98,7 @@ pub struct USBBus {
usb_bus_number: Option<u8>,
// devices are normally hubs
#[serde(rename(deserialize = "_items"))]
devices: Option<Vec<USBDevice>>
devices: Option<Vec<USBDevice>>,
}

pub fn write_devices_recursive(f: &mut fmt::Formatter, devices: &Vec<USBDevice>) -> fmt::Result {
Expand All @@ -116,7 +116,10 @@ pub fn write_devices_recursive(f: &mut fmt::Formatter, devices: &Vec<USBDevice>)
writeln!(f, "{:}", device)?;
}
// print all devices with this device - if hub for example
device.devices.as_ref().map(|d| write_devices_recursive(f, d));
device
.devices
.as_ref()
.map(|d| write_devices_recursive(f, d));
}
Ok(())
}
Expand All @@ -141,17 +144,23 @@ impl fmt::Display for USBBus {

// write the bus details - alternative for coloured and apple info style
if f.alternate() {
writeln!(f, "{:}{:} {:} PCI Device: {:}:{:} Revision: {:04x}",
writeln!(
f,
"{:}{:} {:} PCI Device: {:}:{:} Revision: {:04x}",
tree.bright_black().bold(),
self.name.blue(),
self.host_controller.green(),
format!("{:04x}", self.pci_vendor.unwrap_or(0xffff)).yellow().bold(),
format!("{:04x}", self.pci_vendor.unwrap_or(0xffff))
.yellow()
.bold(),
format!("{:04x}", self.pci_device.unwrap_or(0xffff)).yellow(),
self.pci_revision.unwrap_or(0xffff),
)?;
// lsusb style but not really accurate...
} else {
writeln!(f, "{:}Bus {:03} Device 000 ID {:04x}:{:04x} {:} {:}",
} else {
writeln!(
f,
"{:}Bus {:03} Device 000 ID {:04x}:{:04x} {:} {:}",
tree,
self.usb_bus_number.unwrap_or(0),
self.pci_vendor.unwrap_or(0xffff),
Expand All @@ -166,7 +175,6 @@ impl fmt::Display for USBBus {
}
}


#[derive(Debug, Serialize, Deserialize)]
pub struct USBDevice {
#[serde(rename(deserialize = "_name"))]
Expand All @@ -189,7 +197,7 @@ pub struct USBDevice {
extra_current_used: Option<u8>,
// devices can be hub and have devices attached
#[serde(rename(deserialize = "_items"))]
devices: Option<Vec<USBDevice>>
devices: Option<Vec<USBDevice>>,
}

impl fmt::Display for USBDevice {
Expand All @@ -202,8 +210,17 @@ impl fmt::Display for USBDevice {
// # position on that level
// location_id is "location_reg / port"
let location_split: Vec<&str> = self.location_id.split("/").collect();
let reg = location_split.first().unwrap_or(&"0x00000000").trim().trim_start_matches("0x");
let device_no = location_split.last().unwrap_or(&"0").trim().parse::<u32>().unwrap_or(1);
let reg = location_split
.first()
.unwrap_or(&"0x00000000")
.trim()
.trim_start_matches("0x");
let device_no = location_split
.last()
.unwrap_or(&"0")
.trim()
.parse::<u32>()
.unwrap_or(1);
// bus no is msb
let bus_no = u32::from_str_radix(&reg, 16).unwrap_or(0) >> 24;
// get position in tree based on number of non-zero chars or just 0 if not using tree
Expand Down Expand Up @@ -236,29 +253,37 @@ impl fmt::Display for USBDevice {

// alternate for coloured, slightly different format to lsusb
if f.alternate() {
write!(f, "{:>spaces$}Bus {:} Device {:} ID {:}:{:} {:} {:} {:}",
tree.bright_black(),
format!("{:03}", bus_no).cyan(),
format!("{:03}", device_no).magenta(),
format!("{:04x}", self.vendor_id.unwrap()).yellow().bold(),
format!("{:04x}", self.product_id.unwrap()).yellow(),
self.name.trim().blue(),
self.serial_num.as_ref().unwrap_or(&String::from("None")).trim().green(),
speed.purple()
)
write!(
f,
"{:>spaces$}Bus {:} Device {:} ID {:}:{:} {:} {:} {:}",
tree.bright_black(),
format!("{:03}", bus_no).cyan(),
format!("{:03}", device_no).magenta(),
format!("{:04x}", self.vendor_id.unwrap()).yellow().bold(),
format!("{:04x}", self.product_id.unwrap()).yellow(),
self.name.trim().blue(),
self.serial_num
.as_ref()
.unwrap_or(&String::from("None"))
.trim()
.green(),
speed.purple()
)
// not same data as lsusb when tree (show port, class, driver etc.)
} else {
// add 3 because lsusb is like this
if spaces > 0 {
spaces += 3;
}
write!(f, "{:>spaces$}Bus {:03} Device {:03} ID {:04x}:{:04x} {:}",
tree,
bus_no,
device_no,
self.vendor_id.unwrap_or(0xffff),
self.product_id.unwrap_or(0xffff),
self.name.trim(),
write!(
f,
"{:>spaces$}Bus {:03} Device {:03} ID {:04x}:{:04x} {:}",
tree,
bus_no,
device_no,
self.vendor_id.unwrap_or(0xffff),
self.product_id.unwrap_or(0xffff),
self.name.trim(),
)
}
}
Expand All @@ -267,10 +292,13 @@ impl fmt::Display for USBDevice {
pub fn get_spusb() -> Result<SPUSBDataType, io::Error> {
let output = if cfg!(target_os = "macos") {
Command::new("system_profiler")
.args(["-json", "SPUSBDataType"])
.output()?
.args(["-json", "SPUSBDataType"])
.output()?
} else {
return Err(io::Error::new(io::ErrorKind::Unsupported, "system_profiler is only supported on macOS"))
return Err(io::Error::new(
io::ErrorKind::Unsupported,
"system_profiler is only supported on macOS",
));
};

serde_json::from_str(String::from_utf8(output.stdout).unwrap().as_str())
Expand All @@ -297,8 +325,7 @@ mod tests {
\"vendor_id\" : \"0x2341\"
}";

let device: USBDevice =
serde_json::from_str(device_json).unwrap();
let device: USBDevice = serde_json::from_str(device_json).unwrap();

assert_eq!(device.name, "Arduino Zero");
assert_eq!(device.bcd_device, Some(1.00));
Expand All @@ -323,8 +350,7 @@ mod tests {
\"usb_bus_number\" : \"0x00 \"
}";

let device: USBBus =
serde_json::from_str(device_json).unwrap();
let device: USBBus = serde_json::from_str(device_json).unwrap();

assert_eq!(device.name, "USB31Bus");
assert_eq!(device.host_controller, "AppleUSBXHCITR");
Expand Down

0 comments on commit 9f6fd84

Please sign in to comment.