use clap::ValueEnum;
use colored::*;
use fastrand;
use itertools::Itertools;
use serde::{Deserialize, Serialize};
use std::cmp;
use std::collections::HashMap;
use std::hash::Hash;
use std::io::{self, Write};
use strum::{IntoEnumIterator, VariantArray};
use strum_macros::{Display, EnumIter, VariantArray};
use unicode_width::UnicodeWidthStr;
use crate::colour;
use crate::error::Result;
use crate::icon;
use crate::profiler::{Bus, Device, DeviceFilter, SystemProfile};
use crate::types::NumericalUnit;
use crate::usb::{
path::ConfigurationPath, path::DevicePath, path::EndpointPath, path::PortPath, BaseClass,
ConfigAttributes, Configuration, DeviceExtra, Direction, Endpoint, Interface,
};
const ICON_HEADING: &str = "I";
const DEFAULT_AUTO_WIDTH: u16 = 80; const MIN_VARIABLE_STRING_LEN: usize = 5; const LIST_INSET_SPACES: u8 = 2;
#[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, ValueEnum, Default)]
#[serde(rename_all = "kebab-case")]
pub enum ColorWhen {
#[default]
Auto,
Always,
Never,
}
impl std::fmt::Display for ColorWhen {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{self:?}")
}
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, ValueEnum, Default)]
#[serde(rename_all = "kebab-case")]
pub enum IconWhen {
#[default]
Auto,
Always,
Never,
}
impl std::fmt::Display for IconWhen {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{self:?}")
}
}
impl IconWhen {
fn retain_ref<B: BlockEnum, T>(
&self,
devices: &[&T],
blocks: &mut Vec<impl Block<B, T>>,
settings: &PrintSettings,
) {
match self {
IconWhen::Never => {
blocks.retain(|b| !b.is_icon());
}
IconWhen::Auto => {
let valid_icons = devices
.iter()
.all(|d| has_valid_icons(*d, blocks, settings));
if settings.icons.is_none() || !valid_icons {
log::debug!("{:?} removing icon blocks", settings.icon_when);
blocks.retain(|b| !b.is_icon());
}
}
IconWhen::Always => {
if settings.icons.is_none() {
log::warn!(
"{:?} blocks requested but no icons provided",
settings.icon_when
);
}
}
}
}
fn retain<B: BlockEnum, T>(
&self,
devices: &[T],
blocks: &mut Vec<impl Block<B, T>>,
settings: &PrintSettings,
) {
match self {
IconWhen::Never => {
blocks.retain(|b| !b.is_icon());
}
IconWhen::Auto => {
let valid_icons = devices
.iter()
.all(|d| has_valid_icons(d, blocks, settings));
if settings.icons.is_none() || !valid_icons {
log::debug!("{:?} removing icon blocks", settings.icon_when);
blocks.retain(|b| !b.is_icon());
}
}
IconWhen::Always => {
if settings.icons.is_none() {
log::warn!(
"{:?} blocks requested but no icons provided",
settings.icon_when
);
}
}
}
}
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, ValueEnum, Default)]
#[serde(rename_all = "kebab-case")]
pub enum Encoding {
#[default]
Glyphs,
Utf8,
Ascii,
}
impl std::fmt::Display for Encoding {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{self:?}")
}
}
impl Encoding {
pub fn char_is_valid(&self, c: char) -> bool {
match self {
Encoding::Ascii if !c.is_ascii() => false,
Encoding::Utf8 => !matches!(c,
'\u{E000}'..='\u{F8FF}' |
'\u{F0000}'..='\u{FFFFD}' |
'\u{100000}'..='\u{10FFFD}'),
_ => true,
}
}
pub fn str_is_valid(&self, s: &str) -> bool {
s.chars().all(|c| self.char_is_valid(c))
}
}
#[non_exhaustive]
#[derive(
Debug,
EnumIter,
VariantArray,
ValueEnum,
Display,
Copy,
Eq,
PartialEq,
Ord,
PartialOrd,
Clone,
Hash,
Serialize,
Deserialize,
)]
#[serde(rename_all = "kebab-case")]
pub enum DeviceBlocks {
BusNumber,
DeviceNumber,
BranchPosition,
PortPath,
SysPath,
Driver,
Icon,
VendorId,
ProductId,
VidPid,
Name,
Manufacturer,
ProductName,
VendorName,
Serial,
Speed,
NegotiatedSpeed,
SpeedLabel,
NegotiatedSpeedLabel,
TreePositions,
BusPower,
BusPowerUsed,
ExtraCurrentUsed,
BcdDevice,
BcdUsb,
#[serde(alias = "class-code")] BaseClass,
SubClass,
Protocol,
UidClass,
UidSubClass,
UidProtocol,
Class,
#[serde(alias = "class-value")] BaseValue,
LastEvent,
EventIcon,
OperationMode,
NegotiatedOperationMode,
SpeedMarketingLabel,
NegotiatedSpeedMarketingLabel,
}
#[non_exhaustive]
#[derive(
Debug,
Copy,
EnumIter,
VariantArray,
ValueEnum,
Display,
Eq,
PartialEq,
Ord,
PartialOrd,
Hash,
Clone,
Serialize,
Deserialize,
)]
#[serde(rename_all = "kebab-case")]
pub enum BusBlocks {
BusNumber,
Icon,
Name,
HostController,
HostControllerVendor,
HostControllerDevice,
PciVendor,
PciDevice,
PciRevision,
PortPath,
}
#[non_exhaustive]
#[derive(
Debug,
Copy,
EnumIter,
VariantArray,
ValueEnum,
Display,
Eq,
PartialEq,
Hash,
Clone,
Serialize,
Deserialize,
)]
#[serde(rename_all = "kebab-case")]
pub enum ConfigurationBlocks {
Name,
Number,
NumInterfaces,
Attributes,
IconAttributes,
MaxPower,
}
#[non_exhaustive]
#[derive(
Debug,
Copy,
EnumIter,
VariantArray,
ValueEnum,
Display,
Eq,
PartialEq,
Hash,
Clone,
Serialize,
Deserialize,
)]
#[serde(rename_all = "kebab-case")]
pub enum InterfaceBlocks {
Name,
Number,
PortPath,
#[serde(alias = "class-code")] BaseClass,
SubClass,
Protocol,
AltSetting,
Driver,
SysPath,
DevPath,
MountPaths,
NumEndpoints,
Icon,
UidClass,
UidSubClass,
UidProtocol,
Class,
#[serde(alias = "class-value")]
BaseValue,
}
#[non_exhaustive]
#[derive(
Debug,
Copy,
EnumIter,
VariantArray,
ValueEnum,
Display,
Eq,
PartialEq,
Hash,
Clone,
Serialize,
Deserialize,
)]
#[serde(rename_all = "kebab-case")]
pub enum EndpointBlocks {
Number,
Direction,
TransferType,
SyncType,
UsageType,
MaxPacketSize,
Interval,
}
#[derive(Debug, Eq, PartialEq)]
pub enum BlockLength {
Fixed(usize),
Variable(usize),
}
impl BlockLength {
pub fn len(self) -> usize {
match self {
BlockLength::Fixed(s) => s,
BlockLength::Variable(s) => s,
}
}
pub fn is_empty(self) -> bool {
match self {
BlockLength::Fixed(s) => s == 0,
BlockLength::Variable(s) => s == 0,
}
}
pub fn fixed_len(self) -> Option<usize> {
match self {
BlockLength::Fixed(s) => Some(s),
_ => None,
}
}
pub fn variable_len(self) -> Option<usize> {
match self {
BlockLength::Variable(s) => Some(s),
_ => None,
}
}
}
pub trait BlockEnum: Eq + Hash + VariantArray + ValueEnum {}
impl BlockEnum for DeviceBlocks {}
impl BlockEnum for BusBlocks {}
impl BlockEnum for ConfigurationBlocks {}
impl BlockEnum for InterfaceBlocks {}
impl BlockEnum for EndpointBlocks {}
pub trait Block<B: BlockEnum, T> {
const INSET: u8 = 0;
fn default_blocks(verbose: bool) -> Vec<Self>
where
Self: Sized;
fn example_blocks() -> Vec<Self>
where
Self: Sized,
{
Self::default_blocks(false)
}
fn len(&self, d: &[&T]) -> usize;
fn block_length(&self) -> BlockLength;
fn generate_padding(d: &[&T]) -> HashMap<B, usize>;
fn colour(&self, s: &str, ct: &colour::ColourTheme) -> ColoredString;
fn heading(&self) -> &str;
fn heading_padded(&self, pad: &HashMap<B, usize>) -> String;
fn value_is_variable_length(&self) -> bool {
match self.block_length() {
BlockLength::Fixed(_) => false,
BlockLength::Variable(_) => true,
}
}
fn format_value(
&self,
d: &T,
pad: &HashMap<B, usize>,
settings: &PrintSettings,
) -> Option<String>;
fn format_base_u16(v: u16, settings: &PrintSettings) -> String {
if settings.decimal {
format!("{v:6}")
} else {
format!("0x{v:04x}")
}
}
fn format_base_u8(v: u8, settings: &PrintSettings) -> String {
if settings.decimal {
format!("{v:4}")
} else {
format!("0x{v:02x}")
}
}
fn format_vidpid(v: Option<u16>, p: Option<u16>, settings: &PrintSettings) -> String {
match (v, p) {
(Some(v), Some(p)) => {
if settings.decimal {
format!("{v:>5}:{p:<5}")
} else {
format!(" {v:04x}:{p:04x} ")
}
}
_ => format!("{:>5}:{:<5}", "-", "-"),
}
}
fn is_icon(&self) -> bool {
false
}
fn all_blocks() -> &'static [B]
where
Self: Sized,
{
B::VARIANTS
}
}
impl DeviceBlocks {
pub fn default_watch_blocks(verbose: bool, tree: bool) -> Vec<Self> {
let mut blocks = if tree {
Self::default_device_tree_blocks()
} else {
Self::default_blocks(verbose)
};
blocks.push(DeviceBlocks::EventIcon);
blocks.push(DeviceBlocks::LastEvent);
blocks
}
pub fn default_device_tree_blocks() -> Vec<Self> {
#[cfg(target_os = "linux")]
{
vec![
DeviceBlocks::Icon,
DeviceBlocks::BranchPosition,
DeviceBlocks::DeviceNumber,
DeviceBlocks::VendorId,
DeviceBlocks::ProductId,
DeviceBlocks::Name,
DeviceBlocks::Serial,
DeviceBlocks::Driver,
]
}
#[cfg(not(target_os = "linux"))]
{
vec![
DeviceBlocks::Icon,
DeviceBlocks::BranchPosition,
DeviceBlocks::DeviceNumber,
DeviceBlocks::VendorId,
DeviceBlocks::ProductId,
DeviceBlocks::Name,
DeviceBlocks::Serial,
]
}
}
}
impl Block<DeviceBlocks, Device> for DeviceBlocks {
#[cfg(target_os = "linux")]
fn default_blocks(verbose: bool) -> Vec<Self> {
if verbose {
vec![
DeviceBlocks::BusNumber,
DeviceBlocks::DeviceNumber,
DeviceBlocks::TreePositions,
DeviceBlocks::PortPath,
DeviceBlocks::Icon,
DeviceBlocks::VendorId,
DeviceBlocks::ProductId,
DeviceBlocks::BcdDevice,
DeviceBlocks::BcdUsb,
DeviceBlocks::BaseValue,
DeviceBlocks::BaseClass,
DeviceBlocks::SubClass,
DeviceBlocks::UidSubClass,
DeviceBlocks::Protocol,
DeviceBlocks::UidProtocol,
DeviceBlocks::Name,
DeviceBlocks::Manufacturer,
DeviceBlocks::Serial,
DeviceBlocks::Driver,
DeviceBlocks::SysPath,
DeviceBlocks::Speed,
]
} else {
vec![
DeviceBlocks::BusNumber,
DeviceBlocks::DeviceNumber,
DeviceBlocks::Icon,
DeviceBlocks::VendorId,
DeviceBlocks::ProductId,
DeviceBlocks::Name,
DeviceBlocks::Serial,
DeviceBlocks::Driver,
DeviceBlocks::Speed,
]
}
}
#[cfg(not(target_os = "linux"))]
fn default_blocks(verbose: bool) -> Vec<Self> {
if verbose {
vec![
DeviceBlocks::BusNumber,
DeviceBlocks::DeviceNumber,
DeviceBlocks::TreePositions,
DeviceBlocks::PortPath,
DeviceBlocks::Icon,
DeviceBlocks::VendorId,
DeviceBlocks::ProductId,
DeviceBlocks::BcdDevice,
DeviceBlocks::BcdUsb,
DeviceBlocks::BaseValue,
DeviceBlocks::BaseClass,
DeviceBlocks::SubClass,
DeviceBlocks::UidSubClass,
DeviceBlocks::Protocol,
DeviceBlocks::UidProtocol,
DeviceBlocks::Name,
DeviceBlocks::Manufacturer,
DeviceBlocks::Serial,
DeviceBlocks::Speed,
]
} else {
vec![
DeviceBlocks::BusNumber,
DeviceBlocks::DeviceNumber,
DeviceBlocks::Icon,
DeviceBlocks::VendorId,
DeviceBlocks::ProductId,
DeviceBlocks::Name,
DeviceBlocks::Serial,
DeviceBlocks::Speed,
]
}
}
fn example_blocks() -> Vec<Self> {
vec![
DeviceBlocks::BusNumber,
DeviceBlocks::DeviceNumber,
DeviceBlocks::Icon,
DeviceBlocks::VendorId,
DeviceBlocks::ProductId,
DeviceBlocks::Name,
DeviceBlocks::Serial,
DeviceBlocks::Driver,
DeviceBlocks::Speed,
]
}
fn len(&self, d: &[&Device]) -> usize {
match self {
DeviceBlocks::Name => d.iter().map(|d| d.name.width()).max().unwrap_or(0),
DeviceBlocks::Serial => d
.iter()
.flat_map(|d| d.serial_num.as_ref().map(|s| s.width()))
.max()
.unwrap_or(0),
DeviceBlocks::Manufacturer => d
.iter()
.flat_map(|d| d.manufacturer.as_ref().map(|s| s.width()))
.max()
.unwrap_or(0),
DeviceBlocks::TreePositions => d
.iter()
.map(|d| d.location_id.tree_positions.len() * 2)
.max()
.unwrap_or(0),
DeviceBlocks::PortPath => d
.iter()
.map(|d| d.port_path().to_string().len())
.max()
.unwrap_or(0),
DeviceBlocks::SysPath => d
.iter()
.flat_map(|d| {
d.extra
.as_ref()
.and_then(|e| e.syspath.as_ref().map(|s| s.len()))
})
.max()
.unwrap_or(0),
DeviceBlocks::Driver => d
.iter()
.flat_map(|d| {
d.extra
.as_ref()
.and_then(|e| e.driver.as_ref().map(|s| s.len()))
})
.max()
.unwrap_or(0),
DeviceBlocks::ProductName => d
.iter()
.flat_map(|d| {
d.extra
.as_ref()
.and_then(|e| e.product_name.as_ref().map(|s| s.width()))
})
.max()
.unwrap_or(0),
DeviceBlocks::VendorName => d
.iter()
.flat_map(|d| {
d.extra
.as_ref()
.and_then(|e| e.vendor.as_ref().map(|s| s.width()))
})
.max()
.unwrap_or(0),
DeviceBlocks::BaseClass => d
.iter()
.flat_map(|d| d.class.as_ref().map(|c| c.to_string().len()))
.max()
.unwrap_or(0),
DeviceBlocks::UidClass => d
.iter()
.flat_map(|d| d.class_name().map(|s| s.len()))
.max()
.unwrap_or(0),
DeviceBlocks::UidSubClass => d
.iter()
.flat_map(|d| d.sub_class_name().map(|s| s.len()))
.max()
.unwrap_or(0),
DeviceBlocks::UidProtocol => d
.iter()
.flat_map(|d| d.protocol_name().map(|s| s.len()))
.max()
.unwrap_or(0),
DeviceBlocks::Class => d
.iter()
.map(|d| d.fully_defined_class().map_or(0, |c| c.to_string().len()))
.max()
.unwrap_or(0),
DeviceBlocks::LastEvent => d
.iter()
.flat_map(|d| d.last_event().map(|s| s.to_string().len()))
.max()
.unwrap_or(0),
DeviceBlocks::SpeedLabel => d
.iter()
.flat_map(|d| {
d.device_speed
.as_ref()
.and_then(|s| s.original_label())
.map(|l| l.len())
})
.max()
.unwrap_or(0),
DeviceBlocks::NegotiatedSpeedLabel => d
.iter()
.flat_map(|d| {
d.extra
.as_ref()
.and_then(|e| e.negotiated_speed.as_ref())
.map(|s| s.original_label().len())
})
.max()
.unwrap_or(0),
DeviceBlocks::SpeedMarketingLabel => d
.iter()
.flat_map(|d| {
d.device_speed
.as_ref()
.and_then(|s| s.marketing_label())
.map(|l| l.len())
})
.max()
.unwrap_or(0),
DeviceBlocks::NegotiatedSpeedMarketingLabel => d
.iter()
.flat_map(|d| {
d.extra
.as_ref()
.and_then(|e| e.negotiated_speed.as_ref())
.map(|s| s.marketing_label().len())
})
.max()
.unwrap_or(0),
DeviceBlocks::OperationMode => d
.iter()
.flat_map(|d| {
d.device_speed
.as_ref()
.and_then(|s| s.operation_mode())
.map(|m| m.to_string().len())
})
.max()
.unwrap_or(0),
DeviceBlocks::NegotiatedOperationMode => d
.iter()
.flat_map(|d| {
d.extra
.as_ref()
.and_then(|e| e.negotiated_speed.as_ref())
.and_then(|s| s.operation_mode())
.map(|m| m.to_string().len())
})
.max()
.unwrap_or(0),
_ => self.block_length().len(),
}
}
fn generate_padding(d: &[&Device]) -> HashMap<Self, usize> {
DeviceBlocks::iter()
.map(|b| (b, cmp::max(b.heading().len(), b.len(d))))
.collect()
}
fn format_value(
&self,
d: &Device,
pad: &HashMap<Self, usize>,
settings: &PrintSettings,
) -> Option<String> {
match self {
DeviceBlocks::BusNumber => Some(format!("{:3}", d.location_id.bus)),
DeviceBlocks::DeviceNumber => Some(format!("{:3}", d.location_id.number)),
DeviceBlocks::BranchPosition => Some(format!("{:3}", d.get_branch_position())),
DeviceBlocks::PortPath => Some(format!(
"{:pad$}",
d.port_path().to_string(),
pad = pad.get(self).unwrap_or(&0)
)),
DeviceBlocks::SysPath => Some(match d.extra.as_ref() {
Some(e) => format!(
"{:pad$}",
e.syspath.as_ref().unwrap_or(&format!(
"{:pad$}",
"-",
pad = pad.get(self).unwrap_or(&0)
)),
pad = pad.get(self).unwrap_or(&0)
),
None => format!("{:pad$}", "-", pad = pad.get(self).unwrap_or(&0)),
}),
DeviceBlocks::Driver => Some(match d.extra.as_ref() {
Some(e) => format!(
"{:pad$}",
e.driver.as_ref().unwrap_or(&format!(
"{:pad$}",
"-",
pad = pad.get(self).unwrap_or(&0)
)),
pad = pad.get(self).unwrap_or(&0)
),
None => format!("{:pad$}", "-", pad = pad.get(self).unwrap_or(&0)),
}),
DeviceBlocks::ProductName => Some(match d.extra.as_ref() {
Some(e) => format!(
"{:pad$}",
e.product_name.as_ref().unwrap_or(&format!(
"{:pad$}",
"-",
pad = pad.get(self).unwrap_or(&0)
)),
pad = pad.get(self).unwrap_or(&0)
),
None => format!("{:pad$}", "-", pad = pad.get(self).unwrap_or(&0)),
}),
DeviceBlocks::VendorName => Some(match d.extra.as_ref() {
Some(e) => format!(
"{:pad$}",
e.vendor.as_ref().unwrap_or(&format!(
"{:pad$}",
"-",
pad = pad.get(self).unwrap_or(&0)
)),
pad = pad.get(self).unwrap_or(&0)
),
None => format!("{:pad$}", "-", pad = pad.get(self).unwrap_or(&0)),
}),
DeviceBlocks::Icon => settings.icons.as_ref().map(|i| i.get_device_icon(d)),
DeviceBlocks::VendorId => Some(match d.vendor_id {
Some(v) => Self::format_base_u16(v, settings),
None => format!("{:>6}", "-"),
}),
DeviceBlocks::ProductId => Some(match d.product_id {
Some(v) => Self::format_base_u16(v, settings),
None => format!("{:>6}", "-"),
}),
DeviceBlocks::VidPid => Some(Self::format_vidpid(d.vendor_id, d.product_id, settings)),
DeviceBlocks::Name => Some(format!(
"{:pad$}",
d.name,
pad = pad.get(self).unwrap_or(&0)
)),
DeviceBlocks::Manufacturer => Some(match d.manufacturer.as_ref() {
Some(v) => format!("{:pad$}", v, pad = pad.get(self).unwrap_or(&0)),
None => format!("{:pad$}", "-", pad = pad.get(self).unwrap_or(&0)),
}),
DeviceBlocks::Serial => Some(match d.serial_num.as_ref() {
Some(v) => format!("{:pad$}", v, pad = pad.get(self).unwrap_or(&0)),
None => format!("{:pad$}", "-", pad = pad.get(self).unwrap_or(&0)),
}),
DeviceBlocks::Speed => Some(match d.device_speed.as_ref() {
Some(v) => format!("{:>10}", v.to_string()),
None => format!("{:>10}", "-"),
}),
DeviceBlocks::NegotiatedSpeed => Some(
match d.extra.as_ref().and_then(|e| e.negotiated_speed.as_ref()) {
Some(v) => {
let nu = NumericalUnit::<f32>::from(v);
format!("{:>10}", nu.to_string())
}
None => format!("{:>10}", "-"),
},
),
DeviceBlocks::SpeedLabel => Some(match d.device_speed.as_ref() {
Some(v) => format!(
"{:pad$}",
v.original_label().unwrap_or_else(|| "-".into()),
pad = pad.get(self).unwrap_or(&0)
),
None => format!("{:pad$}", "-", pad = pad.get(self).unwrap_or(&0)),
}),
DeviceBlocks::NegotiatedSpeedLabel => Some(
match d.extra.as_ref().and_then(|e| e.negotiated_speed.as_ref()) {
Some(v) => format!(
"{:pad$}",
v.original_label(),
pad = pad.get(self).unwrap_or(&0)
),
None => format!("{:pad$}", "-", pad = pad.get(self).unwrap_or(&0)),
},
),
DeviceBlocks::SpeedMarketingLabel => Some(match d.device_speed.as_ref() {
Some(v) => format!(
"{:pad$}",
v.marketing_label().unwrap_or_else(|| "-".into()),
pad = pad.get(self).unwrap_or(&0)
),
None => format!("{:pad$}", "-", pad = pad.get(self).unwrap_or(&0)),
}),
DeviceBlocks::NegotiatedSpeedMarketingLabel => Some(
match d.extra.as_ref().and_then(|e| e.negotiated_speed.as_ref()) {
Some(v) => format!(
"{:pad$}",
v.marketing_label(),
pad = pad.get(self).unwrap_or(&0)
),
None => format!("{:pad$}", "-", pad = pad.get(self).unwrap_or(&0)),
},
),
DeviceBlocks::OperationMode => Some(
match d.device_speed.as_ref().and_then(|s| s.operation_mode()) {
Some(v) => format!("{:pad$}", v.to_string(), pad = pad.get(self).unwrap_or(&0)),
None => format!("{:pad$}", "-", pad = pad.get(self).unwrap_or(&0)),
},
),
DeviceBlocks::NegotiatedOperationMode => Some(
match d
.extra
.as_ref()
.and_then(|e| e.negotiated_speed.as_ref())
.and_then(|s| s.operation_mode())
{
Some(v) => format!("{:pad$}", v.to_string(), pad = pad.get(self).unwrap_or(&0)),
None => format!("{:pad$}", "-", pad = pad.get(self).unwrap_or(&0)),
},
),
DeviceBlocks::TreePositions => Some(format!(
"{:pad$}",
format!("{:}", d.location_id.tree_positions.iter().format("-")),
pad = pad.get(self).unwrap_or(&0)
)),
DeviceBlocks::BusPower => Some(match d.bus_power {
Some(v) => format!("{v:3} mA"),
None => format!("{:>6}", "-"),
}),
DeviceBlocks::BusPowerUsed => Some(match d.bus_power_used {
Some(v) => format!("{v:3} mA"),
None => format!("{:>6}", "-"),
}),
DeviceBlocks::ExtraCurrentUsed => Some(match d.extra_current_used {
Some(v) => format!("{v:3} mA"),
None => format!("{:>6}", "-"),
}),
DeviceBlocks::BcdDevice => Some(match d.bcd_device {
Some(v) => format!("{:5}", v.to_string()),
None => format!("{:>5}", "-"),
}),
DeviceBlocks::BcdUsb => Some(match d.bcd_usb {
Some(v) => format!("{:5}", v.to_string()),
None => format!("{:>5}", "-"),
}),
DeviceBlocks::BaseClass => Some(match d.class.as_ref() {
Some(v) => format!("{:pad$}", v.to_string(), pad = pad.get(self).unwrap_or(&0)),
None => format!("{:pad$}", "-", pad = pad.get(self).unwrap_or(&0)),
}),
DeviceBlocks::SubClass => Some(match d.sub_class.as_ref() {
Some(v) => Self::format_base_u8(*v, settings),
None => format!("{:>4}", "-"),
}),
DeviceBlocks::Protocol => Some(match d.protocol.as_ref() {
Some(v) => Self::format_base_u8(*v, settings),
None => format!("{:>4}", "-"),
}),
DeviceBlocks::UidClass => Some(match d.class_name() {
Some(v) => format!("{:pad$}", v, pad = pad.get(self).unwrap_or(&0)),
None => format!("{:pad$}", "-", pad = pad.get(self).unwrap_or(&0)),
}),
DeviceBlocks::UidSubClass => Some(match d.sub_class_name() {
Some(v) => format!("{:pad$}", v, pad = pad.get(self).unwrap_or(&0)),
None => format!("{:pad$}", "-", pad = pad.get(self).unwrap_or(&0)),
}),
DeviceBlocks::UidProtocol => Some(match d.protocol_name() {
Some(v) => format!("{:pad$}", v, pad = pad.get(self).unwrap_or(&0)),
None => format!("{:pad$}", "-", pad = pad.get(self).unwrap_or(&0)),
}),
DeviceBlocks::Class => Some(match d.fully_defined_class() {
Some(v) => format!("{:pad$}", v, pad = pad.get(self).unwrap_or(&0)),
None => format!("{:pad$}", "-", pad = pad.get(self).unwrap_or(&0)),
}),
DeviceBlocks::BaseValue => Some(match d.class.as_ref() {
Some(v) => Self::format_base_u8((*v).into(), settings),
None => format!("{:pad$}", "-", pad = pad.get(self).unwrap_or(&0)),
}),
DeviceBlocks::LastEvent => Some(match d.last_event() {
Some(v) => format!("{:pad$}", v, pad = pad.get(self).unwrap_or(&0)),
None => format!("{:pad$}", "-", pad = pad.get(self).unwrap_or(&0)),
}),
DeviceBlocks::EventIcon => match d.last_event() {
Some(e) => settings.icons.as_ref().map(|i| i.get_event_icon(&e)),
None => None,
},
}
}
fn colour(&self, s: &str, ct: &colour::ColourTheme) -> ColoredString {
match self {
DeviceBlocks::BcdUsb
| DeviceBlocks::BcdDevice
| DeviceBlocks::DeviceNumber
| DeviceBlocks::LastEvent => ct.number.map_or(s.normal(), |c| s.color(c)),
DeviceBlocks::BusNumber
| DeviceBlocks::BranchPosition
| DeviceBlocks::TreePositions => ct.location.map_or(s.normal(), |c| s.color(c)),
DeviceBlocks::Icon | DeviceBlocks::EventIcon => {
ct.icon.map_or(s.normal(), |c| s.color(c))
}
DeviceBlocks::PortPath | DeviceBlocks::SysPath => {
ct.path.map_or(s.normal(), |c| s.color(c))
}
DeviceBlocks::VendorId | DeviceBlocks::VidPid => {
ct.vid.map_or(s.normal(), |c| s.color(c))
}
DeviceBlocks::ProductId => ct.pid.map_or(s.normal(), |c| s.color(c)),
DeviceBlocks::Name | DeviceBlocks::ProductName => {
ct.name.map_or(s.normal(), |c| s.color(c))
}
DeviceBlocks::Serial => ct.serial.map_or(s.normal(), |c| s.color(c)),
DeviceBlocks::Manufacturer | DeviceBlocks::VendorName => {
ct.manufacturer.map_or(s.normal(), |c| s.color(c))
}
DeviceBlocks::Driver => ct.driver.map_or(s.normal(), |c| s.color(c)),
DeviceBlocks::Speed
| DeviceBlocks::NegotiatedSpeed
| DeviceBlocks::SpeedLabel
| DeviceBlocks::NegotiatedSpeedLabel
| DeviceBlocks::SpeedMarketingLabel
| DeviceBlocks::NegotiatedSpeedMarketingLabel
| DeviceBlocks::OperationMode
| DeviceBlocks::NegotiatedOperationMode => ct.speed.map_or(s.normal(), |c| s.color(c)),
DeviceBlocks::BusPower
| DeviceBlocks::BusPowerUsed
| DeviceBlocks::ExtraCurrentUsed => ct.power.map_or(s.normal(), |c| s.color(c)),
DeviceBlocks::BaseClass
| DeviceBlocks::UidClass
| DeviceBlocks::Class
| DeviceBlocks::BaseValue => ct.class_code.map_or(s.normal(), |c| s.color(c)),
DeviceBlocks::SubClass | DeviceBlocks::UidSubClass => {
ct.sub_code.map_or(s.normal(), |c| s.color(c))
}
DeviceBlocks::Protocol | DeviceBlocks::UidProtocol => {
ct.protocol.map_or(s.normal(), |c| s.color(c))
}
}
}
fn heading(&self) -> &str {
match self {
DeviceBlocks::BusNumber => "Bus",
DeviceBlocks::DeviceNumber => "#",
DeviceBlocks::BranchPosition => "Prt",
DeviceBlocks::PortPath => "PPath",
DeviceBlocks::SysPath => "SPath",
DeviceBlocks::Driver => "Driver",
DeviceBlocks::VendorId => "VID",
DeviceBlocks::ProductId => "PID",
DeviceBlocks::VidPid => "VID:PID",
DeviceBlocks::Name => "Name",
DeviceBlocks::Manufacturer => "Manfacturer",
DeviceBlocks::ProductName => "PName",
DeviceBlocks::VendorName => "VName",
DeviceBlocks::Serial => "Serial",
DeviceBlocks::Speed => "Speed",
DeviceBlocks::NegotiatedSpeed => "NgSpd",
DeviceBlocks::SpeedLabel => "SgSpd",
DeviceBlocks::NegotiatedSpeedLabel => "NSSpd",
DeviceBlocks::SpeedMarketingLabel => "MktSpd",
DeviceBlocks::NegotiatedSpeedMarketingLabel => "NgMktSpd",
DeviceBlocks::OperationMode => "OpMode",
DeviceBlocks::NegotiatedOperationMode => "NgOpMode",
DeviceBlocks::TreePositions => "TPos",
DeviceBlocks::BusPower => "PBus",
DeviceBlocks::BusPowerUsed => "PUsd",
DeviceBlocks::ExtraCurrentUsed => "PExr",
DeviceBlocks::BcdDevice => "Dev V",
DeviceBlocks::BcdUsb => "USB V",
DeviceBlocks::BaseClass => "BaseC",
DeviceBlocks::SubClass => "SubC",
DeviceBlocks::Protocol => "Pcol",
DeviceBlocks::UidClass => "UidCl",
DeviceBlocks::UidSubClass => "UidSc",
DeviceBlocks::UidProtocol => "UidPc",
DeviceBlocks::Class => "Class",
DeviceBlocks::BaseValue => "CVal",
DeviceBlocks::Icon => ICON_HEADING,
DeviceBlocks::EventIcon => "E",
DeviceBlocks::LastEvent => "Event",
}
}
fn heading_padded(&self, pad: &HashMap<Self, usize>) -> String {
format!(
"{:^pad$}",
self.heading(),
pad = pad.get(self).unwrap_or(&0)
)
}
fn block_length(&self) -> BlockLength {
match self {
DeviceBlocks::Icon | DeviceBlocks::EventIcon => BlockLength::Fixed(1),
DeviceBlocks::BusNumber | DeviceBlocks::DeviceNumber | DeviceBlocks::BranchPosition => {
BlockLength::Fixed(3)
}
DeviceBlocks::VendorId | DeviceBlocks::ProductId => BlockLength::Fixed(6),
DeviceBlocks::VidPid => BlockLength::Fixed(11),
DeviceBlocks::Speed => BlockLength::Fixed(10),
DeviceBlocks::NegotiatedSpeed => BlockLength::Fixed(10),
DeviceBlocks::BusPower
| DeviceBlocks::BusPowerUsed
| DeviceBlocks::ExtraCurrentUsed => BlockLength::Fixed(6),
DeviceBlocks::BcdDevice | DeviceBlocks::BcdUsb => BlockLength::Fixed(5),
DeviceBlocks::SubClass | DeviceBlocks::Protocol | DeviceBlocks::BaseValue => {
BlockLength::Fixed(4)
}
_ => BlockLength::Variable(self.heading().len()),
}
}
fn is_icon(&self) -> bool {
self == &DeviceBlocks::Icon
}
}
impl Block<BusBlocks, Bus> for BusBlocks {
fn default_blocks(verbose: bool) -> Vec<Self> {
if verbose {
vec![
BusBlocks::Icon,
BusBlocks::PortPath,
BusBlocks::Name,
BusBlocks::HostController,
BusBlocks::HostControllerDevice,
BusBlocks::HostControllerVendor,
BusBlocks::PciVendor,
BusBlocks::PciDevice,
BusBlocks::PciRevision,
]
} else {
vec![
BusBlocks::PortPath,
BusBlocks::Name,
BusBlocks::HostController,
BusBlocks::HostControllerDevice,
]
}
}
fn len(&self, d: &[&Bus]) -> usize {
match self {
BusBlocks::Name => d.iter().map(|d| d.name.width()).max().unwrap_or(0),
BusBlocks::HostController => d
.iter()
.map(|d| d.host_controller.width())
.max()
.unwrap_or(0),
BusBlocks::HostControllerVendor => d
.iter()
.flat_map(|d| d.host_controller_vendor.as_ref().map(|v| v.width()))
.max()
.unwrap_or(0),
BusBlocks::HostControllerDevice => d
.iter()
.flat_map(|d| d.host_controller_device.as_ref().map(|v| v.width()))
.max()
.unwrap_or(0),
BusBlocks::PortPath => d
.iter()
.map(|d| d.path().unwrap_or_default().as_os_str().len())
.max()
.unwrap_or(0),
_ => self.block_length().len(),
}
}
fn generate_padding(d: &[&Bus]) -> HashMap<Self, usize> {
BusBlocks::iter()
.map(|b| (b, cmp::max(b.heading().len(), b.len(d))))
.collect()
}
fn colour(&self, s: &str, ct: &colour::ColourTheme) -> ColoredString {
match self {
BusBlocks::BusNumber => ct.location.map_or(s.normal(), |c| s.color(c)),
BusBlocks::PciVendor => ct.vid.map_or(s.normal(), |c| s.color(c)),
BusBlocks::PciDevice => ct.pid.map_or(s.normal(), |c| s.color(c)),
BusBlocks::Name => ct.name.map_or(s.normal(), |c| s.color(c)),
BusBlocks::HostController => ct.class_code.map_or(s.normal(), |c| s.color(c)),
BusBlocks::HostControllerVendor => ct.manufacturer.map_or(s.normal(), |c| s.color(c)),
BusBlocks::HostControllerDevice => ct.name.map_or(s.normal(), |c| s.color(c)),
BusBlocks::PciRevision => ct.number.map_or(s.normal(), |c| s.color(c)),
BusBlocks::Icon => ct.icon.map_or(s.normal(), |c| s.color(c)),
BusBlocks::PortPath => ct.path.map_or(s.normal(), |c| s.color(c)),
}
}
fn format_value(
&self,
bus: &Bus,
pad: &HashMap<Self, usize>,
settings: &PrintSettings,
) -> Option<String> {
match self {
BusBlocks::BusNumber => bus
.get_bus_number()
.map(|v| format!("{v:3}"))
.or(Some("---".to_string())),
BusBlocks::Icon => settings
.icons
.as_ref()
.map(|i| i.get_bus_icon(bus))
.or(Some(" ".to_string())),
BusBlocks::PciVendor => Some(match bus.pci_vendor {
Some(v) => Self::format_base_u16(v, settings),
None => format!("{:>6}", "-"),
}),
BusBlocks::PciDevice => Some(match bus.pci_device {
Some(v) => Self::format_base_u16(v, settings),
None => format!("{:>6}", "-"),
}),
BusBlocks::PciRevision => Some(match bus.pci_revision {
Some(v) => Self::format_base_u16(v, settings),
None => format!("{:>6}", "-"),
}),
BusBlocks::Name => Some(format!(
"{:pad$}",
bus.name,
pad = pad.get(self).unwrap_or(&0)
)),
BusBlocks::HostController => Some(format!(
"{:pad$}",
bus.host_controller,
pad = pad.get(self).unwrap_or(&0)
)),
BusBlocks::HostControllerVendor => Some(match bus.host_controller_vendor.as_ref() {
Some(v) => format!("{:pad$}", v, pad = pad.get(self).unwrap_or(&0)),
None => format!("{:pad$}", "-", pad = pad.get(self).unwrap_or(&0)),
}),
BusBlocks::HostControllerDevice => Some(match bus.host_controller_device.as_ref() {
Some(v) => format!("{:pad$}", v, pad = pad.get(self).unwrap_or(&0)),
None => format!("{:pad$}", "-", pad = pad.get(self).unwrap_or(&0)),
}),
BusBlocks::PortPath => Some(match bus.path() {
Some(v) => format!("{:pad$}", v.display(), pad = pad.get(self).unwrap_or(&0)),
None => format!("{:pad$}", "-", pad = pad.get(self).unwrap_or(&0)),
}),
}
}
fn heading(&self) -> &str {
match self {
BusBlocks::BusNumber => "Bus",
BusBlocks::PortPath => "PPath",
BusBlocks::PciDevice => "VID",
BusBlocks::PciVendor => "PID",
BusBlocks::PciRevision => "Revisn",
BusBlocks::Name => "Name",
BusBlocks::HostController => "HostController",
BusBlocks::HostControllerVendor => "HostVendor",
BusBlocks::HostControllerDevice => "HostDevice",
BusBlocks::Icon => ICON_HEADING,
}
}
fn heading_padded(&self, pad: &HashMap<Self, usize>) -> String {
format!(
"{:^pad$}",
self.heading(),
pad = pad.get(self).unwrap_or(&0)
)
}
fn block_length(&self) -> BlockLength {
match self {
BusBlocks::Icon => BlockLength::Fixed(1),
BusBlocks::BusNumber => BlockLength::Fixed(3),
BusBlocks::PciDevice | BusBlocks::PciVendor | BusBlocks::PciRevision => {
BlockLength::Fixed(6)
}
_ => BlockLength::Variable(self.heading().len()),
}
}
fn is_icon(&self) -> bool {
self == &BusBlocks::Icon
}
}
impl Block<ConfigurationBlocks, Configuration> for ConfigurationBlocks {
const INSET: u8 = 1;
fn default_blocks(verbose: bool) -> Vec<Self> {
if verbose {
vec![
ConfigurationBlocks::Number,
ConfigurationBlocks::IconAttributes,
ConfigurationBlocks::Attributes,
ConfigurationBlocks::NumInterfaces,
ConfigurationBlocks::MaxPower,
ConfigurationBlocks::Name,
]
} else {
vec![
ConfigurationBlocks::Number,
ConfigurationBlocks::IconAttributes,
ConfigurationBlocks::MaxPower,
ConfigurationBlocks::Name,
]
}
}
fn len(&self, d: &[&Configuration]) -> usize {
match self {
ConfigurationBlocks::Name => d.iter().map(|d| d.name.len()).max().unwrap_or(0),
ConfigurationBlocks::Attributes => d
.iter()
.map(|d| d.attributes_string().len())
.max()
.unwrap_or(0),
_ => self.block_length().len(),
}
}
fn generate_padding(d: &[&Configuration]) -> HashMap<Self, usize> {
ConfigurationBlocks::iter()
.map(|b| (b, cmp::max(b.heading().len(), b.len(d))))
.collect()
}
fn colour(&self, s: &str, ct: &colour::ColourTheme) -> ColoredString {
match self {
ConfigurationBlocks::Number => ct.location.map_or(s.normal(), |c| s.color(c)),
ConfigurationBlocks::NumInterfaces => ct.number.map_or(s.normal(), |c| s.color(c)),
ConfigurationBlocks::MaxPower => ct.power.map_or(s.normal(), |c| s.color(c)),
ConfigurationBlocks::Name => ct.name.map_or(s.normal(), |c| s.color(c)),
ConfigurationBlocks::Attributes => ct.attributes.map_or(s.normal(), |c| s.color(c)),
ConfigurationBlocks::IconAttributes => ct.icon.map_or(s.normal(), |c| s.color(c)),
}
}
fn format_value(
&self,
config: &Configuration,
pad: &HashMap<Self, usize>,
settings: &PrintSettings,
) -> Option<String> {
match self {
ConfigurationBlocks::Number => Some(format!("{:2}", config.number)),
ConfigurationBlocks::NumInterfaces => Some(format!("{:2}", config.interfaces.len())),
ConfigurationBlocks::Name => Some(format!(
"{:pad$}",
config.name,
pad = pad.get(self).unwrap_or(&0)
)),
ConfigurationBlocks::MaxPower => Some(format!("{:6}", config.max_power)),
ConfigurationBlocks::Attributes => Some(format!(
"{:pad$}",
config.attributes_string(),
pad = pad.get(self).unwrap_or(&0)
)),
ConfigurationBlocks::IconAttributes => Some(format!(
"{:pad$}",
attributes_to_icons(&config.attributes, settings),
pad = pad.get(self).unwrap_or(&0)
)),
}
}
fn heading(&self) -> &str {
match self {
ConfigurationBlocks::Number => "#",
ConfigurationBlocks::NumInterfaces => "I#",
ConfigurationBlocks::MaxPower => "PMax",
ConfigurationBlocks::Name => "Name",
ConfigurationBlocks::Attributes => "Attributes",
ConfigurationBlocks::IconAttributes => ICON_HEADING,
}
}
fn heading_padded(&self, pad: &HashMap<Self, usize>) -> String {
format!(
"{:^pad$}",
self.heading(),
pad = pad.get(self).unwrap_or(&0)
)
}
fn block_length(&self) -> BlockLength {
match self {
ConfigurationBlocks::Number => BlockLength::Fixed(2),
ConfigurationBlocks::NumInterfaces => BlockLength::Fixed(2),
ConfigurationBlocks::MaxPower => BlockLength::Fixed(6),
ConfigurationBlocks::IconAttributes => BlockLength::Fixed(3),
_ => BlockLength::Variable(self.heading().len()),
}
}
fn is_icon(&self) -> bool {
self == &ConfigurationBlocks::IconAttributes
}
}
impl Block<InterfaceBlocks, Interface> for InterfaceBlocks {
const INSET: u8 = 2;
#[cfg(target_os = "linux")]
fn default_blocks(verbose: bool) -> Vec<Self> {
if verbose {
vec![
InterfaceBlocks::PortPath,
InterfaceBlocks::Icon,
InterfaceBlocks::AltSetting,
InterfaceBlocks::BaseValue,
InterfaceBlocks::BaseClass,
InterfaceBlocks::SubClass,
InterfaceBlocks::UidSubClass,
InterfaceBlocks::Protocol,
InterfaceBlocks::UidProtocol,
InterfaceBlocks::Name,
InterfaceBlocks::NumEndpoints,
InterfaceBlocks::Driver,
InterfaceBlocks::DevPath,
InterfaceBlocks::SysPath,
]
} else {
vec![
InterfaceBlocks::PortPath,
InterfaceBlocks::Icon,
InterfaceBlocks::AltSetting,
InterfaceBlocks::BaseClass,
InterfaceBlocks::SubClass,
InterfaceBlocks::Protocol,
InterfaceBlocks::Name,
InterfaceBlocks::Driver,
]
}
}
#[cfg(not(target_os = "linux"))]
fn default_blocks(verbose: bool) -> Vec<Self> {
if verbose {
vec![
InterfaceBlocks::PortPath,
InterfaceBlocks::Icon,
InterfaceBlocks::AltSetting,
InterfaceBlocks::BaseValue,
InterfaceBlocks::BaseClass,
InterfaceBlocks::SubClass,
InterfaceBlocks::UidSubClass,
InterfaceBlocks::Protocol,
InterfaceBlocks::UidProtocol,
InterfaceBlocks::Name,
InterfaceBlocks::NumEndpoints,
]
} else {
vec![
InterfaceBlocks::PortPath,
InterfaceBlocks::Icon,
InterfaceBlocks::AltSetting,
InterfaceBlocks::BaseClass,
InterfaceBlocks::SubClass,
InterfaceBlocks::Protocol,
InterfaceBlocks::Name,
]
}
}
fn example_blocks() -> Vec<Self> {
vec![
InterfaceBlocks::PortPath,
InterfaceBlocks::Icon,
InterfaceBlocks::AltSetting,
InterfaceBlocks::BaseClass,
InterfaceBlocks::SubClass,
InterfaceBlocks::Protocol,
InterfaceBlocks::Name,
InterfaceBlocks::Driver,
]
}
fn len(&self, d: &[&Interface]) -> usize {
match self {
InterfaceBlocks::Name => d
.iter()
.flat_map(|d| d.name.as_ref().map(|s| s.width()))
.max()
.unwrap_or(0),
InterfaceBlocks::BaseClass => d
.iter()
.map(|d| d.class.to_string().len())
.max()
.unwrap_or(0),
InterfaceBlocks::PortPath => d.iter().map(|d| d.path.len()).max().unwrap_or(0),
InterfaceBlocks::SysPath => d
.iter()
.flat_map(|d| d.syspath.as_ref().map(|v| v.len()))
.max()
.unwrap_or(0),
InterfaceBlocks::Driver => d
.iter()
.flat_map(|d| d.driver.as_ref().map(|v| v.len()))
.max()
.unwrap_or(0),
InterfaceBlocks::DevPath => d
.iter()
.flat_map(|d| {
d.dev_paths()
.map(|paths| paths.iter().map(|p| p.to_string_lossy()).join(",").len())
})
.max()
.unwrap_or(0),
InterfaceBlocks::MountPaths => d
.iter()
.flat_map(|d| render_mount_paths(d).map(|v| v.len()))
.max()
.unwrap_or(0),
InterfaceBlocks::UidClass => d
.iter()
.flat_map(|d| d.class_name().map(|s| s.len()))
.max()
.unwrap_or(0),
InterfaceBlocks::UidSubClass => d
.iter()
.flat_map(|d| d.sub_class_name().map(|s| s.len()))
.max()
.unwrap_or(0),
InterfaceBlocks::UidProtocol => d
.iter()
.flat_map(|d| d.protocol_name().map(|s| s.len()))
.max()
.unwrap_or(0),
InterfaceBlocks::Class => d
.iter()
.map(|d| d.fully_defined_class().to_string().len())
.max()
.unwrap_or(0),
_ => self.block_length().len(),
}
}
fn generate_padding(d: &[&Interface]) -> HashMap<Self, usize> {
InterfaceBlocks::iter()
.map(|b| (b, cmp::max(b.heading().len(), b.len(d))))
.collect()
}
fn colour(&self, s: &str, ct: &colour::ColourTheme) -> ColoredString {
match self {
InterfaceBlocks::Number => ct.number.map_or(s.normal(), |c| s.color(c)),
InterfaceBlocks::Name => ct.name.map_or(s.normal(), |c| s.color(c)),
InterfaceBlocks::PortPath
| InterfaceBlocks::SysPath
| InterfaceBlocks::DevPath
| InterfaceBlocks::MountPaths => ct.path.map_or(s.normal(), |c| s.color(c)),
InterfaceBlocks::Icon => ct.icon.map_or(s.normal(), |c| s.color(c)),
InterfaceBlocks::BaseClass
| InterfaceBlocks::UidClass
| InterfaceBlocks::Class
| InterfaceBlocks::BaseValue => ct.class_code.map_or(s.normal(), |c| s.color(c)),
InterfaceBlocks::SubClass | InterfaceBlocks::UidSubClass => {
ct.sub_code.map_or(s.normal(), |c| s.color(c))
}
InterfaceBlocks::Protocol | InterfaceBlocks::UidProtocol => {
ct.protocol.map_or(s.normal(), |c| s.color(c))
}
InterfaceBlocks::Driver => ct.driver.map_or(s.normal(), |c| s.color(c)),
InterfaceBlocks::AltSetting | InterfaceBlocks::NumEndpoints => {
ct.number.map_or(s.normal(), |c| s.color(c))
}
}
}
fn format_value(
&self,
interface: &Interface,
pad: &HashMap<Self, usize>,
settings: &PrintSettings,
) -> Option<String> {
match self {
InterfaceBlocks::Number => Some(format!("{:2}", interface.number)),
InterfaceBlocks::Name => Some(match interface.name.as_ref() {
Some(v) => format!("{:pad$}", v, pad = pad.get(self).unwrap_or(&0)),
None => format!("{:pad$}", "-", pad = pad.get(self).unwrap_or(&0)),
}),
InterfaceBlocks::NumEndpoints => Some(format!("{:2}", interface.endpoints.len())),
InterfaceBlocks::PortPath => Some(format!(
"{:pad$}",
interface.path,
pad = pad.get(self).unwrap_or(&0)
)),
InterfaceBlocks::SysPath => Some(match interface.syspath.as_ref() {
Some(v) => format!("{:pad$}", v, pad = pad.get(self).unwrap_or(&0)),
None => format!("{:pad$}", "-", pad = pad.get(self).unwrap_or(&0)),
}),
InterfaceBlocks::Driver => Some(match interface.driver.as_ref() {
Some(v) => format!("{:pad$}", v, pad = pad.get(self).unwrap_or(&0)),
None => format!("{:pad$}", "-", pad = pad.get(self).unwrap_or(&0)),
}),
InterfaceBlocks::DevPath => Some(match interface.dev_paths() {
Some(v) => format!(
"{:pad$}",
v.iter().map(|p| p.to_string_lossy()).join(","),
pad = pad.get(self).unwrap_or(&0)
),
None => format!("{:pad$}", "-", pad = pad.get(self).unwrap_or(&0)),
}),
InterfaceBlocks::MountPaths => Some(match render_mount_paths(interface) {
Some(v) => format!("{:pad$}", v, pad = pad.get(self).unwrap_or(&0)),
None => format!("{:pad$}", "-", pad = pad.get(self).unwrap_or(&0)),
}),
InterfaceBlocks::BaseClass => Some(format!(
"{:pad$}",
interface.class.to_string(),
pad = pad.get(self).unwrap_or(&0)
)),
InterfaceBlocks::SubClass => Some(Self::format_base_u8(interface.sub_class, settings)),
InterfaceBlocks::Protocol => Some(Self::format_base_u8(interface.protocol, settings)),
InterfaceBlocks::AltSetting => {
Some(Self::format_base_u8(interface.alt_setting, settings))
}
InterfaceBlocks::Icon => settings.icons.as_ref().map(|i| {
i.get_classifier_icon(&interface.class, interface.sub_class, interface.protocol)
}),
InterfaceBlocks::UidClass => Some(match interface.class_name() {
Some(v) => format!("{:pad$}", v, pad = pad.get(self).unwrap_or(&0)),
None => format!("{:pad$}", "-", pad = pad.get(self).unwrap_or(&0)),
}),
InterfaceBlocks::UidSubClass => Some(match interface.sub_class_name() {
Some(v) => format!("{:pad$}", v, pad = pad.get(self).unwrap_or(&0)),
None => format!("{:pad$}", "-", pad = pad.get(self).unwrap_or(&0)),
}),
InterfaceBlocks::UidProtocol => Some(match interface.protocol_name() {
Some(v) => format!("{:pad$}", v, pad = pad.get(self).unwrap_or(&0)),
None => format!("{:pad$}", "-", pad = pad.get(self).unwrap_or(&0)),
}),
InterfaceBlocks::Class => Some(format!(
"{:pad$}",
interface.fully_defined_class(),
pad = pad.get(self).unwrap_or(&0)
)),
InterfaceBlocks::BaseValue => {
Some(Self::format_base_u8(interface.class.into(), settings))
}
}
}
fn heading(&self) -> &str {
match self {
InterfaceBlocks::Number => "#",
InterfaceBlocks::Name => "Name",
InterfaceBlocks::NumEndpoints => "E#",
InterfaceBlocks::PortPath => "PPath",
InterfaceBlocks::SysPath => "SPath",
InterfaceBlocks::Driver => "Driver",
InterfaceBlocks::DevPath => "DPath",
InterfaceBlocks::MountPaths => "MPaths",
InterfaceBlocks::BaseClass => "BaseC",
InterfaceBlocks::SubClass => "SubC",
InterfaceBlocks::Protocol => "Pcol",
InterfaceBlocks::AltSetting => "Alt#",
InterfaceBlocks::UidClass => "UidCl",
InterfaceBlocks::UidSubClass => "UidSc",
InterfaceBlocks::UidProtocol => "UidPc",
InterfaceBlocks::Class => "Class",
InterfaceBlocks::BaseValue => "CVal",
InterfaceBlocks::Icon => ICON_HEADING,
}
}
fn heading_padded(&self, pad: &HashMap<Self, usize>) -> String {
format!(
"{:^pad$}",
self.heading(),
pad = pad.get(self).unwrap_or(&0)
)
}
fn block_length(&self) -> BlockLength {
match self {
InterfaceBlocks::Icon => BlockLength::Fixed(1),
InterfaceBlocks::Number => BlockLength::Fixed(2),
InterfaceBlocks::NumEndpoints => BlockLength::Fixed(2),
InterfaceBlocks::SubClass
| InterfaceBlocks::Protocol
| InterfaceBlocks::AltSetting
| InterfaceBlocks::BaseValue => BlockLength::Fixed(4),
_ => BlockLength::Variable(self.heading().len()),
}
}
fn is_icon(&self) -> bool {
self == &InterfaceBlocks::Icon
}
}
impl Block<EndpointBlocks, Endpoint> for EndpointBlocks {
const INSET: u8 = 3;
fn default_blocks(verbose: bool) -> Vec<Self> {
if verbose {
vec![
EndpointBlocks::Number,
EndpointBlocks::Direction,
EndpointBlocks::TransferType,
EndpointBlocks::SyncType,
EndpointBlocks::UsageType,
EndpointBlocks::Interval,
EndpointBlocks::MaxPacketSize,
]
} else {
vec![
EndpointBlocks::Number,
EndpointBlocks::Direction,
EndpointBlocks::TransferType,
EndpointBlocks::SyncType,
EndpointBlocks::UsageType,
EndpointBlocks::MaxPacketSize,
]
}
}
fn len(&self, d: &[&Endpoint]) -> usize {
match self {
EndpointBlocks::TransferType => d
.iter()
.map(|d| d.transfer_type.to_string().len())
.max()
.unwrap_or(0),
EndpointBlocks::SyncType => d
.iter()
.map(|d| d.sync_type.to_string().len())
.max()
.unwrap_or(0),
EndpointBlocks::UsageType => d
.iter()
.map(|d| d.usage_type.to_string().len())
.max()
.unwrap_or(0),
EndpointBlocks::Direction => d
.iter()
.map(|d| d.address.direction.to_string().len())
.max()
.unwrap_or(0),
EndpointBlocks::MaxPacketSize => d
.iter()
.map(|d| d.max_packet_string().len())
.max()
.unwrap_or(0),
_ => self.block_length().len(),
}
}
fn generate_padding(d: &[&Endpoint]) -> HashMap<Self, usize> {
EndpointBlocks::iter()
.map(|b| (b, cmp::max(b.heading().len(), b.len(d))))
.collect()
}
fn colour(&self, s: &str, ct: &colour::ColourTheme) -> ColoredString {
match self {
EndpointBlocks::Number | EndpointBlocks::Interval | EndpointBlocks::MaxPacketSize => {
ct.number.map_or(s.normal(), |c| s.color(c))
}
EndpointBlocks::Direction
| EndpointBlocks::UsageType
| EndpointBlocks::TransferType
| EndpointBlocks::SyncType => ct.attributes.map_or(s.normal(), |c| s.color(c)),
}
}
fn format_value(
&self,
end: &Endpoint,
pad: &HashMap<Self, usize>,
_settings: &PrintSettings,
) -> Option<String> {
match self {
EndpointBlocks::Number => Some(format!("{:2}", end.address.number)),
EndpointBlocks::Interval => Some(format!("{:2}", end.interval)),
EndpointBlocks::MaxPacketSize => Some(format!(
"{:pad$}",
end.max_packet_string(),
pad = pad.get(self).unwrap_or(&0)
)),
EndpointBlocks::Direction => Some(format!(
"{:pad$}",
end.address.direction.to_string(),
pad = pad.get(self).unwrap_or(&0)
)),
EndpointBlocks::TransferType => Some(format!(
"{:pad$}",
end.transfer_type.to_string(),
pad = pad.get(self).unwrap_or(&0)
)),
EndpointBlocks::SyncType => Some(format!(
"{:pad$}",
end.sync_type.to_string(),
pad = pad.get(self).unwrap_or(&0)
)),
EndpointBlocks::UsageType => Some(format!(
"{:pad$}",
end.usage_type.to_string(),
pad = pad.get(self).unwrap_or(&0)
)),
}
}
fn heading(&self) -> &str {
match self {
EndpointBlocks::Number => "#",
EndpointBlocks::Interval => "Iv",
EndpointBlocks::MaxPacketSize => "MaxPkb",
EndpointBlocks::Direction => "Dir",
EndpointBlocks::TransferType => "TranT",
EndpointBlocks::SyncType => "SyncT",
EndpointBlocks::UsageType => "UsgeT",
}
}
fn heading_padded(&self, pad: &HashMap<Self, usize>) -> String {
format!(
"{:^pad$}",
self.heading(),
pad = pad.get(self).unwrap_or(&0)
)
}
fn block_length(&self) -> BlockLength {
match self {
EndpointBlocks::Number => BlockLength::Fixed(2),
EndpointBlocks::Interval => BlockLength::Fixed(2),
_ => BlockLength::Variable(self.heading().len()),
}
}
}
#[derive(Default, PartialEq, Eq, Debug, ValueEnum, Clone, Copy, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum Sort {
#[default]
DeviceNumber,
BranchPosition,
NoSort,
}
impl Sort {
pub fn sort_devices(&self, devices: &mut [Device]) {
match self {
Sort::BranchPosition => {
devices.sort_by_key(|d| d.get_branch_position() + d.location_id.bus)
}
Sort::DeviceNumber => devices.sort_by_key(|d| d.location_id.number + d.location_id.bus),
_ => (),
}
}
pub fn sort_devices_ref(&self, devices: &mut [&Device]) {
match self {
Sort::BranchPosition => {
devices.sort_by_key(|d| d.get_branch_position() + d.location_id.bus)
}
Sort::DeviceNumber => devices.sort_by_key(|d| d.location_id.number + d.location_id.bus),
_ => (),
}
}
pub fn sort_devices_recursive(&self, devices: &mut Vec<Device>) {
self.sort_devices(devices);
for device in devices {
if let Some(branch_devices) = &mut device.devices {
self.sort_devices_recursive(branch_devices);
}
}
}
pub fn sort_bus(&self, bus: &mut Bus) {
if matches!(self, Sort::NoSort) {
return;
}
if let Some(devices) = &mut bus.devices {
self.sort_devices_recursive(devices);
}
}
pub fn sort_buses(&self, buses: &mut Vec<Bus>) {
buses.sort_by_key(|b| b.get_bus_number());
for bus in buses {
self.sort_bus(bus);
}
}
}
#[derive(Default, Debug, ValueEnum, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum Group {
#[default]
NoGroup,
Bus,
}
#[derive(Default, Debug, ValueEnum, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum MaskSerial {
NoMask,
#[default]
Hide,
Scramble,
Replace,
}
#[derive(Default, Debug, ValueEnum, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum PrintMode {
#[default]
Normal,
Dynamic,
}
#[derive(Debug, Default)]
pub struct PrintSettings {
pub no_padding: bool,
pub decimal: bool,
pub tree: bool,
pub sort_devices: Sort,
pub sort_buses: bool,
pub group_devices: Group,
pub headings: bool,
pub verbosity: u8,
pub more: bool,
pub json: bool,
pub encoding: Encoding,
pub mask_serials: Option<MaskSerial>,
pub device_blocks: Option<Vec<DeviceBlocks>>,
pub bus_blocks: Option<Vec<BusBlocks>>,
pub config_blocks: Option<Vec<ConfigurationBlocks>>,
pub interface_blocks: Option<Vec<InterfaceBlocks>>,
pub endpoint_blocks: Option<Vec<EndpointBlocks>>,
pub icons: Option<icon::IconTheme>,
pub colours: Option<colour::ColourTheme>,
pub max_variable_string_len: Option<usize>,
pub auto_width: bool,
pub terminal_size: Option<(u16, u16)>,
pub icon_when: IconWhen,
pub color_when: ColorWhen,
pub print_mode: PrintMode,
pub mute_hubs: bool,
}
fn attributes_to_icons(attributes: &Vec<ConfigAttributes>, settings: &PrintSettings) -> String {
let mut icon_strs = Vec::new();
if settings.icons.is_some() {
for a in attributes {
match a {
ConfigAttributes::SelfPowered => icon_strs.push("\u{f06a5}"), ConfigAttributes::RemoteWakeup => icon_strs.push("\u{f0155}"), ConfigAttributes::BatteryPowered => icon_strs.push("\u{f244}"), ConfigAttributes::BusPowered => icon_strs.push("\u{f11f0}"), }
}
}
icon_strs.join(" ")
}
pub fn truncate_string(s: &mut String, len: usize) {
if s.width() <= len || len <= 3 {
return;
}
if let Some((i, _)) = s.char_indices().nth(len - 3) {
s.truncate(i);
s.push_str("...");
}
}
pub fn auto_max_string_len<B: BlockEnum, T>(
blocks: &[impl Block<B, T>],
offset: usize,
#[allow(clippy::ptr_arg)] variable_lens: &Vec<usize>,
settings: &PrintSettings,
) -> Option<usize> {
if variable_lens.is_empty() {
return None;
}
let total_fixed: usize = blocks
.iter()
.filter_map(|b| b.block_length().fixed_len())
.sum::<usize>()
+ blocks.len()
+ offset;
let total_variable: usize = variable_lens.iter().sum();
let total_len: usize = total_fixed + total_variable + (blocks.len() * 2);
let (width, height) = settings.terminal_size.unwrap_or((DEFAULT_AUTO_WIDTH, 0));
log::trace!(
"Auto scaling running for max length {total_len:?} of which fixed {total_fixed:?}, to terminal size {width:?} {height:?}"
);
let w = width as usize;
if total_len > w {
if w < total_fixed {
log::trace!("Cannot scale, fixed already taking all space!");
return Some(MIN_VARIABLE_STRING_LEN);
}
let variable_len_remain: usize = w - total_fixed;
let mut auto_max_string = variable_len_remain / (variable_lens.len());
let mut remaining_chars: usize = variable_lens
.iter()
.filter(|v| **v <= auto_max_string)
.map(|v| auto_max_string - v)
.sum();
log::trace!(
"Auto max string calculated {auto_max_string:?}, remaining {remaining_chars:?}"
);
let variable_longer = variable_lens
.iter()
.filter(|v| **v > auto_max_string)
.count();
remaining_chars = remaining_chars
.checked_div(variable_longer)
.unwrap_or(remaining_chars);
auto_max_string += remaining_chars;
if auto_max_string < MIN_VARIABLE_STRING_LEN {
log::trace!(
"Ignoring auto max string {auto_max_string:?}! Clamped to MIN_VARIABLE_STRING_LEN {MIN_VARIABLE_STRING_LEN:?}"
);
Some(MIN_VARIABLE_STRING_LEN)
} else {
log::trace!("Final auto max string {auto_max_string:?}");
Some(auto_max_string)
}
} else {
None
}
}
pub fn has_valid_icons<B: BlockEnum, T>(
d: &T,
blocks: &[impl Block<B, T>],
settings: &PrintSettings,
) -> bool {
blocks.iter().filter(|b| b.is_icon()).all(|b| {
if log::log_enabled!(log::Level::Trace) {
let val = b.format_value(d, &HashMap::new(), settings);
let ret = match &val {
Some(v) => settings.encoding.str_is_valid(v),
None => false,
};
log::trace!(
"icon {:?} valid for {:?}: {:?}",
val,
settings.encoding,
ret
);
ret
} else {
match b.format_value(d, &HashMap::new(), settings) {
Some(v) => settings.encoding.str_is_valid(&v),
None => false,
}
}
})
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum LineStyle {
#[default]
Normal,
Dimmed,
Muted,
}
pub fn render_value<B: BlockEnum, T>(
d: &T,
blocks: &[impl Block<B, T>],
pad: &HashMap<B, usize>,
settings: &PrintSettings,
max_string_length: Option<usize>,
line_style: LineStyle,
) -> Vec<String> {
let mut ret = Vec::new();
for b in blocks {
if let Some(mut string) = b.format_value(d, pad, settings) {
if b.value_is_variable_length() {
if let Some(ml) = max_string_length {
truncate_string(&mut string, ml)
}
}
match &settings.colours {
Some(c) => match line_style {
LineStyle::Dimmed => ret.push(format!("{}", string.dimmed().white())),
LineStyle::Muted => ret.push(format!(
"{}",
c.muted.map_or(string.normal(), |hc| string.color(hc))
)),
LineStyle::Normal => ret.push(format!("{}", b.colour(&string, c))),
},
None => ret.push(string.to_string()),
};
}
}
ret
}
pub fn render_heading<B: BlockEnum, T>(
blocks: &[impl Block<B, T>],
pad: &HashMap<B, usize>,
max_string_length: Option<usize>,
) -> Vec<String> {
let mut ret = Vec::new();
for b in blocks {
let mut string = b.heading_padded(pad);
if b.value_is_variable_length() {
if let Some(ml) = max_string_length {
truncate_string(&mut string, ml)
}
}
ret.push(string)
}
ret
}
pub fn render_mount_paths(interface: &Interface) -> Option<String> {
interface.block_mount_paths().map(|paths| {
paths
.iter()
.map(|(num, path)| {
if let Ok(part) = num.strip_prefix("/dev/") {
format!("{}:{}", part.display(), path.display())
} else {
format!(":{}", path.display())
}
})
.collect::<Vec<String>>()
.join(", ")
})
}
fn generate_tree_data(
current_tree: &TreeData,
branch_length: usize,
index: usize,
settings: &PrintSettings,
) -> TreeData {
let mut pass_tree = current_tree.clone();
if settings.tree {
pass_tree.prefix = if pass_tree.depth > 0 {
let edge_icon = if index + 1 != pass_tree.branch_length {
icon::Icon::TreeLine
} else {
icon::Icon::TreeBlank
};
format!(
"{}{}",
pass_tree.prefix,
settings.icons.as_ref().map_or(
icon::get_default_tree_icon(&edge_icon, &settings.encoding),
|i| i.get_tree_icon(&edge_icon, &settings.encoding)
)
)
} else {
pass_tree.prefix.to_string()
};
}
pass_tree.depth += 1;
pass_tree.branch_length = branch_length;
pass_tree.trunk_index = index as u8;
pass_tree
}
fn generate_extra_blocks(
extra: &DeviceExtra,
settings: &PrintSettings,
) -> (
Vec<ConfigurationBlocks>,
Vec<InterfaceBlocks>,
Vec<EndpointBlocks>,
) {
let mut blocks = (
settings.config_blocks.to_owned().unwrap_or(
Block::<ConfigurationBlocks, Configuration>::default_blocks(settings.more),
),
settings.interface_blocks.to_owned().unwrap_or(
Block::<InterfaceBlocks, Interface>::default_blocks(settings.more),
),
settings.endpoint_blocks.to_owned().unwrap_or(
Block::<EndpointBlocks, Endpoint>::default_blocks(settings.more),
),
);
match settings.icon_when {
IconWhen::Never | IconWhen::Auto if settings.icons.is_none() => {
blocks.0.retain(|b| !b.is_icon());
blocks.1.retain(|b| !b.is_icon());
blocks.2.retain(|b| !b.is_icon());
}
IconWhen::Auto if settings.encoding == Encoding::Glyphs => (),
IconWhen::Always => {
if settings.icons.is_none() {
log::warn!(
"{:?} blocks requested but no icons provided",
settings.icon_when
);
}
}
_ => {
settings
.icon_when
.retain(&extra.configurations, &mut blocks.0, settings);
extra.configurations.iter().for_each(|c| {
settings
.icon_when
.retain(&c.interfaces, &mut blocks.1, settings);
c.interfaces.iter().for_each(|i| {
settings
.icon_when
.retain(&i.endpoints, &mut blocks.2, settings);
});
});
}
}
blocks
}
#[derive(Debug, Default, Clone)]
pub struct TreeData {
branch_length: usize,
trunk_index: u8,
depth: usize,
prefix: String,
}
#[derive(Default, PartialEq, Eq, Debug, ValueEnum, Clone, Copy, Serialize, Deserialize)]
pub enum BlockOperation {
#[default]
Add,
Append,
New,
Prepend,
Remove,
}
impl BlockOperation {
pub fn new_or_op<B: BlockEnum + Block<B, T>, T>(
&self,
blocks: Option<Vec<B>>,
new: &[B],
verbose: bool,
) -> Result<Vec<B>> {
if matches!(self, BlockOperation::New) {
return Ok(new.to_vec());
}
let mut current = blocks.unwrap_or_else(|| B::default_blocks(verbose));
self.run(&mut current, new)?;
Ok(current)
}
pub fn run<T: BlockEnum>(&self, blocks: &mut Vec<T>, new: &[T]) -> Result<()> {
match self {
BlockOperation::New => {
*blocks = new.to_vec();
}
BlockOperation::Append => {
blocks.extend(new.iter().cloned());
}
BlockOperation::Prepend => {
let mut new = new.to_vec();
new.append(blocks);
*blocks = new;
}
BlockOperation::Add => {
for b in new {
if !blocks.contains(b) {
blocks.push(b.clone());
}
}
}
BlockOperation::Remove => {
for b in new {
blocks.retain(|x| x != b);
}
}
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum LineItem {
Bus(usize),
Device(PortPath),
Config(ConfigurationPath),
Interface(DevicePath),
Endpoint(EndpointPath),
None,
}
pub struct DisplayWriter<W: Write> {
raw_mode: bool,
line_context: Vec<LineItem>,
inner: W,
}
impl<W: Write> Write for DisplayWriter<W> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.inner.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.inner.flush()
}
}
impl Default for DisplayWriter<io::Stdout> {
fn default() -> Self {
Self::new(io::stdout())
}
}
impl<W: Write> DisplayWriter<W> {
pub fn new(inner: W) -> Self {
Self {
raw_mode: false,
line_context: Vec::new(),
inner,
}
}
pub fn set_raw_mode(&mut self, raw_mode: bool) {
self.raw_mode = raw_mode;
}
pub fn print<S: AsRef<str>>(&mut self, text: S) -> io::Result<()> {
write!(self.inner, "{}", text.as_ref())?;
self.inner.flush()?;
Ok(())
}
pub fn println<S: AsRef<str>>(&mut self, text: S, item: LineItem) -> io::Result<()> {
if self.raw_mode {
write!(self.inner, "{}\r\n", text.as_ref())?;
} else {
writeln!(self.inner, "{}", text.as_ref())?;
}
self.line_context.push(item);
self.inner.flush()?;
Ok(())
}
pub fn into_inner(self) -> W {
self.inner
}
pub fn line_context(&self) -> &Vec<LineItem> {
&self.line_context
}
pub fn print_endpoints(
&mut self,
interface: &Interface,
blocks: &[EndpointBlocks],
settings: &PrintSettings,
tree: &TreeData,
line_style: LineStyle,
) {
let endpoints = &interface.endpoints;
let device_path = interface.device_path();
let mut pad = if !settings.no_padding {
let endpoints: Vec<&Endpoint> = endpoints.iter().collect();
EndpointBlocks::generate_padding(&endpoints)
} else {
HashMap::new()
};
pad.retain(|k, _| blocks.contains(k));
let max_variable_string_len: Option<usize> = if settings.auto_width {
let offset = if settings.tree {
tree.depth * 3 + 1
} else {
(EndpointBlocks::INSET * LIST_INSET_SPACES) as usize
};
let variable_lens: Vec<usize> = pad
.iter()
.filter(|(k, _)| k.value_is_variable_length())
.map(|(_, v)| *v)
.collect();
auto_max_string_len(blocks, offset, &variable_lens, settings)
.or(settings.max_variable_string_len)
} else {
settings.max_variable_string_len
};
log::trace!("Print endpoints padding {pad:?}, tree {tree:?}");
if let Some(ml) = max_variable_string_len.as_ref() {
for (k, v) in pad.iter_mut() {
if k.value_is_variable_length() {
*v = cmp::min(*v, *ml);
}
}
}
for (i, endpoint) in endpoints.iter().enumerate() {
let line_item = if let Some(dp) = device_path.as_ref() {
LineItem::Endpoint(EndpointPath::new_with_device_path(
dp.to_owned(),
endpoint.address.address,
))
} else {
LineItem::None
};
if settings.tree {
let mut prefix = if tree.depth > 0 {
let edge_icon = if i + 1 != tree.branch_length {
icon::Icon::TreeEdge
} else {
icon::Icon::TreeCorner
};
let edge = settings.icons.as_ref().map_or(
icon::get_default_tree_icon(&edge_icon, &settings.encoding),
|i| i.get_tree_icon(&edge_icon, &settings.encoding),
);
format!("{}{}", tree.prefix, edge)
} else {
tree.prefix.to_string()
};
let mut terminator = settings.icons.as_ref().map_or(
icon::get_default_tree_icon(
&icon::Icon::Endpoint(endpoint.address.direction),
&settings.encoding,
),
|i| {
i.get_tree_icon(
&icon::Icon::Endpoint(endpoint.address.direction),
&settings.encoding,
)
},
);
if let Some(ct) = settings.colours.as_ref() {
prefix = ct
.tree
.map_or(prefix.normal(), |c| prefix.color(c))
.to_string();
terminator = if endpoint.address.direction == Direction::In {
ct.tree_endpoint_in
.map_or(terminator.normal(), |c| terminator.color(c))
.to_string()
} else {
ct.tree_endpoint_out
.map_or(terminator.normal(), |c| terminator.color(c))
.to_string()
};
}
if settings.headings && i == 0 {
let heading = render_heading(blocks, &pad, max_variable_string_len).join(" ");
self.println(
format!("{} {}", prefix, heading.bold().underline()),
LineItem::None,
)
.unwrap();
}
self.print(format!("{prefix}{terminator} ")).unwrap();
self.println(
render_value(
endpoint,
blocks,
&pad,
settings,
max_variable_string_len,
line_style,
)
.join(" "),
line_item,
)
.unwrap();
} else {
if settings.headings && i == 0 {
let heading = render_heading(blocks, &pad, max_variable_string_len).join(" ");
self.println(
format!("{:spaces$}{}", "", heading.bold().underline(), spaces = 6),
LineItem::None,
)
.unwrap();
}
self.println(
format!(
"{:spaces$}{}",
"",
render_value(
endpoint,
blocks,
&pad,
settings,
max_variable_string_len,
line_style,
)
.join(" "),
spaces = (EndpointBlocks::INSET * LIST_INSET_SPACES) as usize
),
line_item,
)
.unwrap();
}
}
}
pub fn print_interfaces(
&mut self,
interfaces: &[Interface],
blocks: (&Vec<InterfaceBlocks>, &Vec<EndpointBlocks>),
settings: &PrintSettings,
tree: &TreeData,
line_style: LineStyle,
) {
let mut pad = if !settings.no_padding {
let interfaces: Vec<&Interface> = interfaces.iter().collect();
InterfaceBlocks::generate_padding(&interfaces)
} else {
HashMap::new()
};
pad.retain(|k, _| blocks.0.contains(k));
let max_variable_string_len: Option<usize> = if settings.auto_width {
let offset = if settings.tree {
tree.depth * 3 + 1
} else {
(InterfaceBlocks::INSET * LIST_INSET_SPACES) as usize
};
let variable_lens: Vec<usize> = pad
.iter()
.filter(|(k, _)| k.value_is_variable_length())
.map(|(_, v)| *v)
.collect();
auto_max_string_len(blocks.0, offset, &variable_lens, settings)
.or(settings.max_variable_string_len)
} else {
settings.max_variable_string_len
};
if let Some(ml) = max_variable_string_len.as_ref() {
for (k, v) in pad.iter_mut() {
if k.value_is_variable_length() {
*v = cmp::min(*v, *ml);
}
}
}
log::trace!("Print interfaces padding {pad:?}, tree {tree:?}");
for (i, interface) in interfaces.iter().enumerate() {
let line_item = if let Some(dp) = interface.device_path() {
LineItem::Interface(dp)
} else {
LineItem::None
};
if settings.tree {
let mut prefix = if tree.depth > 0 {
let edge_icon = if i + 1 != tree.branch_length {
icon::Icon::TreeEdge
} else {
icon::Icon::TreeCorner
};
let edge = settings.icons.as_ref().map_or(
icon::get_default_tree_icon(&edge_icon, &settings.encoding),
|i| i.get_tree_icon(&edge_icon, &settings.encoding),
);
format!("{}{}", tree.prefix, edge)
} else {
tree.prefix.to_string()
};
let mut terminator = settings.icons.as_ref().map_or(
icon::get_default_tree_icon(
&icon::Icon::TreeInterfaceTerminator,
&settings.encoding,
),
|i| i.get_tree_icon(&icon::Icon::TreeInterfaceTerminator, &settings.encoding),
);
if let Some(ct) = settings.colours.as_ref() {
prefix = ct
.tree
.map_or(prefix.normal(), |c| prefix.color(c))
.to_string();
terminator = ct
.tree_interface_terminator
.map_or(terminator.normal(), |c| terminator.color(c))
.to_string();
}
if settings.headings && i == 0 {
let heading = render_heading(blocks.0, &pad, max_variable_string_len).join(" ");
self.println(
format!("{} {}", prefix, heading.bold().underline()),
LineItem::None,
)
.unwrap();
}
self.print(format!("{prefix}{terminator} ")).unwrap();
self.println(
render_value(
interface,
blocks.0,
&pad,
settings,
max_variable_string_len,
line_style,
)
.join(" "),
line_item,
)
.unwrap();
} else {
if settings.headings && i == 0 {
let heading = render_heading(blocks.0, &pad, max_variable_string_len).join(" ");
self.println(
format!("{:spaces$}{}", "", heading.bold().underline(), spaces = 4),
LineItem::None,
)
.unwrap();
}
self.println(
format!(
"{:spaces$}{}",
"",
render_value(
interface,
blocks.0,
&pad,
settings,
max_variable_string_len,
line_style,
)
.join(" "),
spaces = (InterfaceBlocks::INSET * LIST_INSET_SPACES) as usize
),
line_item,
)
.unwrap();
}
if settings.verbosity >= 3 || interface.is_expanded() {
self.print_endpoints(
interface,
blocks.1,
settings,
&generate_tree_data(tree, interface.endpoints.len(), i, settings),
line_style,
);
}
}
}
pub fn print_configurations(
&mut self,
device: &Device,
configs: &[Configuration],
blocks: (
&Vec<ConfigurationBlocks>,
&Vec<InterfaceBlocks>,
&Vec<EndpointBlocks>,
),
settings: &PrintSettings,
tree: &TreeData,
) {
let mut pad = if !settings.no_padding {
let configs: Vec<&Configuration> = configs.iter().collect();
ConfigurationBlocks::generate_padding(&configs)
} else {
HashMap::new()
};
pad.retain(|k, _| blocks.0.contains(k));
let max_variable_string_len: Option<usize> = if settings.auto_width {
let offset = if settings.tree {
tree.depth * 3 + 1
} else {
(ConfigurationBlocks::INSET * LIST_INSET_SPACES) as usize
};
let variable_lens: Vec<usize> = pad
.iter()
.filter(|(k, _)| k.value_is_variable_length())
.map(|(_, v)| *v)
.collect();
auto_max_string_len(blocks.0, offset, &variable_lens, settings)
.or(settings.max_variable_string_len)
} else {
settings.max_variable_string_len
};
if let Some(ml) = max_variable_string_len.as_ref() {
for (k, v) in pad.iter_mut() {
if k.value_is_variable_length() {
*v = cmp::min(*v, *ml);
}
}
}
log::trace!("Print configs padding {pad:?}, tree {tree:?}");
for (i, config) in configs.iter().enumerate() {
let line_item = LineItem::Config((device.port_path(), config.number));
if settings.tree {
let mut prefix = if tree.depth > 0 {
let edge_icon = if i + 1 != tree.branch_length {
icon::Icon::TreeEdge
} else {
icon::Icon::TreeCorner
};
let edge = settings.icons.as_ref().map_or(
icon::get_default_tree_icon(&edge_icon, &settings.encoding),
|i| i.get_tree_icon(&edge_icon, &settings.encoding),
);
format!("{}{}", tree.prefix, edge)
} else {
tree.prefix.to_string()
};
let mut terminator = settings.icons.as_ref().map_or(
icon::get_default_tree_icon(
&icon::Icon::TreeConfigurationTerminator,
&settings.encoding,
),
|i| {
i.get_tree_icon(
&icon::Icon::TreeConfigurationTerminator,
&settings.encoding,
)
},
);
if let Some(ct) = settings.colours.as_ref() {
prefix = ct
.tree
.map_or(prefix.normal(), |c| prefix.color(c))
.to_string();
terminator = ct
.tree_configuration_terminator
.map_or(terminator.normal(), |c| terminator.color(c))
.to_string();
}
if settings.headings && i == 0 {
let heading = render_heading(blocks.0, &pad, max_variable_string_len).join(" ");
self.println(
format!("{} {}", prefix, heading.bold().underline()),
LineItem::None,
)
.unwrap();
}
self.print(format!("{prefix}{terminator} ")).unwrap();
let ls = if device.is_disconnected() {
LineStyle::Dimmed
} else {
LineStyle::Normal
};
self.println(
render_value(
config,
blocks.0,
&pad,
settings,
max_variable_string_len,
ls,
)
.join(" "),
line_item,
)
.unwrap();
} else {
if settings.headings && i == 0 {
let heading = render_heading(blocks.0, &pad, max_variable_string_len).join(" ");
self.println(
format!("{:spaces$}{}", "", heading.bold().underline(), spaces = 2),
LineItem::None,
)
.unwrap();
}
let ls = if device.is_disconnected() {
LineStyle::Dimmed
} else {
LineStyle::Normal
};
self.println(
format!(
"{:spaces$}{}",
"",
render_value(
config,
blocks.0,
&pad,
settings,
max_variable_string_len,
ls,
)
.join(" "),
spaces = (ConfigurationBlocks::INSET * LIST_INSET_SPACES) as usize
),
line_item,
)
.unwrap();
}
if settings.verbosity >= 2 || config.is_expanded() {
let ls = if device.is_disconnected() {
LineStyle::Dimmed
} else {
LineStyle::Normal
};
self.print_interfaces(
&config.interfaces,
((blocks.1), (blocks.2)),
settings,
&generate_tree_data(tree, config.interfaces.len(), i, settings),
ls,
);
}
}
}
pub fn print_devices(
&mut self,
devices: &[Device],
db: &Vec<DeviceBlocks>,
settings: &PrintSettings,
tree: &TreeData,
padding: &HashMap<DeviceBlocks, usize>,
) {
let mut padding = padding.clone();
let max_variable_string_len: Option<usize> = if settings.auto_width {
let offset = if settings.tree { tree.depth * 3 + 1 } else { 0 };
let variable_lens: Vec<usize> = padding
.iter()
.filter(|(k, _)| k.value_is_variable_length())
.map(|(_, v)| *v)
.collect();
auto_max_string_len(db, offset, &variable_lens, settings)
.or(settings.max_variable_string_len)
} else {
settings.max_variable_string_len
};
if let Some(ml) = max_variable_string_len.as_ref() {
for (k, v) in padding.iter_mut() {
if k.value_is_variable_length() {
*v = cmp::min(*v, *ml);
}
}
}
log::trace!("Print devices padding {padding:?}, tree {tree:?}");
for (i, device) in devices.iter().filter(|d| !d.is_hidden()).enumerate() {
if settings.tree {
let mut prefix = if tree.depth > 0 {
let edge_icon = if i + 1 != tree.branch_length {
icon::Icon::TreeEdge
} else {
icon::Icon::TreeCorner
};
let edge = settings.icons.as_ref().map_or(
icon::get_default_tree_icon(&edge_icon, &settings.encoding),
|i| i.get_tree_icon(&edge_icon, &settings.encoding),
);
format!("{}{}", tree.prefix, edge)
} else {
tree.prefix.to_string()
};
let icon_terminator = if device.is_disconnected() {
icon::Icon::TreeDisconnectedTerminator
} else if device.class == Some(BaseClass::Hub) {
icon::Icon::TreeHubTerminator
} else {
icon::Icon::TreeDeviceTerminator
};
let mut terminator = settings.icons.as_ref().map_or(
icon::get_default_tree_icon(&icon_terminator, &settings.encoding),
|i| i.get_tree_icon(&icon_terminator, &settings.encoding),
);
if let Some(ct) = settings.colours.as_ref() {
prefix = ct
.tree
.map_or(prefix.normal(), |c| prefix.color(c))
.to_string();
terminator = ct
.tree_bus_terminator
.map_or(terminator.normal(), |c| terminator.color(c))
.to_string();
}
if settings.headings && i == 0 {
let heading = render_heading(db, &padding, max_variable_string_len).join(" ");
self.println(
format!("{} {}", prefix, heading.bold().underline()),
LineItem::None,
)
.unwrap();
}
self.print(format!("{prefix}{terminator} ")).unwrap();
} else if settings.headings && i == 0 {
let heading = render_heading(db, &padding, max_variable_string_len).join(" ");
self.println(format!("{}", heading.bold().underline()), LineItem::None)
.unwrap();
}
let line_style = if device.is_disconnected() {
LineStyle::Dimmed
} else if settings.mute_hubs && device.class == Some(BaseClass::Hub) {
LineStyle::Muted
} else {
LineStyle::Normal
};
let device_string = render_value(
device,
db,
&padding,
settings,
max_variable_string_len,
line_style,
)
.join(" ");
self.println(&device_string, LineItem::Device(device.port_path()))
.unwrap();
if let Some(extra) = device.extra.as_ref() {
if settings.verbosity >= 1 || device.is_expanded() {
let blocks = generate_extra_blocks(extra, settings);
let num = device
.devices
.as_ref()
.map_or(0, |d| d.iter().filter(|d| !d.is_hidden()).count());
self.print_configurations(
device,
&extra.configurations,
(&blocks.0, &blocks.1, &blocks.2),
settings,
&generate_tree_data(tree, extra.configurations.len() + num, i, settings),
);
}
} else if settings.verbosity >= 1 {
log::warn!(
"Unable to print verbose information for {device} because libusb extra data is missing"
)
}
if let Some(d) = device.devices.as_ref() {
self.print_devices(
d,
db,
settings,
&generate_tree_data(
tree,
d.iter().filter(|dd| !dd.is_hidden()).count(),
i,
settings,
),
&padding,
);
}
}
}
pub fn print_sp_usb(&mut self, sp_usb: &SystemProfile, settings: &PrintSettings) {
let mut bb = settings
.bus_blocks
.to_owned()
.unwrap_or(Block::<BusBlocks, Bus>::default_blocks(settings.more));
let mut db = settings
.device_blocks
.to_owned()
.unwrap_or(if settings.more {
DeviceBlocks::default_blocks(true)
} else if settings.tree {
DeviceBlocks::default_device_tree_blocks()
} else {
DeviceBlocks::default_blocks(false)
});
match settings.icon_when {
IconWhen::Never | IconWhen::Auto if settings.icons.is_none() => {
bb.retain(|b| !b.is_icon());
db.retain(|b| !b.is_icon());
}
IconWhen::Auto if settings.encoding == Encoding::Glyphs => (),
IconWhen::Always => {
if settings.icons.is_none() {
log::warn!(
"{:?} blocks requested but no icons provided",
settings.icon_when
);
}
}
_ => {
settings.icon_when.retain(&sp_usb.buses, &mut bb, settings);
sp_usb.buses.iter().for_each(|bo| {
bo.devices
.iter()
.for_each(|b| settings.icon_when.retain(b, &mut db, settings));
});
}
}
let base_tree = TreeData {
..Default::default()
};
let mut pad: HashMap<BusBlocks, usize> = if !settings.no_padding {
BusBlocks::generate_padding(&sp_usb.buses.iter().collect::<Vec<&Bus>>())
} else {
HashMap::new()
};
pad.retain(|k, _| bb.contains(k));
let max_variable_string_len: Option<usize> = if settings.auto_width {
let variable_lens: Vec<usize> = pad
.iter()
.filter(|(k, _)| k.value_is_variable_length())
.map(|(_, v)| *v)
.collect();
auto_max_string_len(&bb, base_tree.depth * 3, &variable_lens, settings)
.or(settings.max_variable_string_len)
} else {
settings.max_variable_string_len
};
if let Some(ml) = max_variable_string_len.as_ref() {
for (k, v) in pad.iter_mut() {
if k.value_is_variable_length() {
*v = cmp::min(*v, *ml);
}
}
}
log::trace!(
"print system profile with settings: {settings:?}; padding: {pad:?}; tree {base_tree:?}"
);
let len = sp_usb.buses.iter().filter(|b| !b.is_hidden()).count();
for (i, bus) in sp_usb.buses.iter().filter(|b| !b.is_hidden()).enumerate() {
if settings.tree {
let mut prefix = base_tree.prefix.to_owned();
let mut start = settings.icons.as_ref().map_or(
icon::get_default_tree_icon(&icon::Icon::TreeBusStart, &settings.encoding),
|i| i.get_tree_icon(&icon::Icon::TreeBusStart, &settings.encoding),
);
if let Some(ct) = settings.colours.as_ref() {
prefix = ct
.tree
.map_or(prefix.normal(), |c| prefix.color(c))
.to_string();
start = ct
.tree_bus_start
.map_or(start.normal(), |c| start.color(c))
.to_string();
}
if settings.headings {
let heading = render_heading(&bb, &pad, max_variable_string_len).join(" ");
self.println(
format!("{:>spaces$}{}", "", heading.bold().underline(), spaces = 2),
LineItem::Bus(i),
)
.unwrap();
}
self.print(format!("{prefix}{start} ")).unwrap()
} else if settings.headings {
let heading = render_heading(&bb, &pad, max_variable_string_len).join(" ");
self.println(format!("{}", heading.bold().underline()), LineItem::Bus(i))
.unwrap();
}
self.println(
render_value(
bus,
&bb,
&pad,
settings,
max_variable_string_len,
LineStyle::Normal,
)
.join(" "),
LineItem::Bus(i),
)
.unwrap();
if let Some(d) = bus.devices.as_ref() {
let num = d.iter().filter(|d| !d.is_hidden()).count();
let tree = generate_tree_data(&base_tree, num, i, settings);
let mut padding = if !settings.no_padding {
if settings.tree {
let devices = d
.iter()
.filter(|d| !d.is_hidden())
.collect::<Vec<&Device>>();
DeviceBlocks::generate_padding(&devices)
} else {
let devices = bus
.flattened_devices()
.into_iter()
.filter(|d| !d.is_hidden())
.collect::<Vec<&Device>>();
DeviceBlocks::generate_padding(&devices)
}
} else {
HashMap::new()
};
padding.retain(|k, _| db.contains(k));
self.print_devices(d, &db, settings, &tree, &padding);
}
if i + 1 != len {
self.println("", LineItem::None).unwrap();
}
}
}
pub fn print_flattened_devices(&mut self, devices: &[&Device], settings: &PrintSettings) {
let mut db = settings
.device_blocks
.to_owned()
.unwrap_or(DeviceBlocks::default_blocks(settings.more));
match settings.icon_when {
IconWhen::Never | IconWhen::Auto if settings.icons.is_none() => {
db.retain(|b| !b.is_icon());
}
IconWhen::Auto if settings.encoding == Encoding::Glyphs => (),
IconWhen::Always => {
if settings.icons.is_none() {
log::warn!(
"{:?} blocks requested but no icons provided",
settings.icon_when
);
}
}
_ => settings.icon_when.retain_ref(devices, &mut db, settings),
}
let mut pad = if !settings.no_padding {
DeviceBlocks::generate_padding(devices)
} else {
HashMap::new()
};
pad.retain(|k, _| db.contains(k));
log::trace!("Flattened devices padding {pad:?}");
let max_variable_string_len: Option<usize> = if settings.auto_width {
let variable_lens: Vec<usize> = pad
.iter()
.filter(|(k, _)| k.value_is_variable_length())
.map(|(_, v)| *v)
.collect();
auto_max_string_len(&db, 0, &variable_lens, settings)
.or(settings.max_variable_string_len)
} else {
settings.max_variable_string_len
};
if let Some(ml) = max_variable_string_len.as_ref() {
for (k, v) in pad.iter_mut() {
if k.value_is_variable_length() {
*v = cmp::min(*v, *ml);
}
}
}
if settings.headings {
let heading = render_heading(&db, &pad, max_variable_string_len).join(" ");
println!("{}", heading.bold().underline());
}
for (i, device) in devices.iter().enumerate() {
let line_style = if device.is_disconnected() {
LineStyle::Dimmed
} else if settings.mute_hubs && device.class == Some(BaseClass::Hub) {
LineStyle::Muted
} else {
LineStyle::Normal
};
println!(
"{}",
render_value(
*device,
&db,
&pad,
settings,
max_variable_string_len,
line_style,
)
.join(" ")
);
if let Some(extra) = device.extra.as_ref() {
if settings.verbosity >= 1 || device.is_expanded() {
let blocks = generate_extra_blocks(extra, settings);
self.print_configurations(
device,
&extra.configurations,
(&blocks.0, &blocks.1, &blocks.2),
settings,
&generate_tree_data(
&Default::default(),
extra.configurations.len()
+ device
.devices
.as_ref()
.map_or(0, |d| d.iter().filter(|d| !d.is_hidden()).count()),
i,
settings,
),
);
}
} else if settings.verbosity >= 1 {
log::warn!(
"Unable to print verbose information for {device} because libusb extra data is missing"
)
}
}
}
pub fn print_bus_grouped(
&mut self,
bus_devices: Vec<(&Bus, Vec<&Device>)>,
settings: &PrintSettings,
) {
let bb = settings
.bus_blocks
.to_owned()
.unwrap_or(Block::<BusBlocks, Bus>::default_blocks(settings.more));
let mut pad: HashMap<BusBlocks, usize> = if !settings.no_padding {
let buses: Vec<&Bus> = bus_devices.iter().map(|bd| bd.0).collect();
BusBlocks::generate_padding(&buses)
} else {
HashMap::new()
};
pad.retain(|k, _| bb.contains(k));
let max_variable_string_len: Option<usize> = if settings.auto_width {
let mut variable_lens = pad.clone();
variable_lens.retain(|k, _| k.value_is_variable_length());
auto_max_string_len(&bb, 0, &variable_lens.into_values().collect(), settings)
.or(settings.max_variable_string_len)
} else {
settings.max_variable_string_len
};
if let Some(ml) = max_variable_string_len.as_ref() {
for (k, v) in pad.iter_mut() {
if k.value_is_variable_length() {
*v = cmp::min(*v, *ml);
}
}
}
let len = bus_devices.len();
for (i, (bus, devices)) in bus_devices.into_iter().enumerate() {
if settings.headings {
let heading = render_heading(&bb, &pad, max_variable_string_len).join(" ");
self.println(format!("{}", heading.bold().underline()), LineItem::Bus(i))
.unwrap();
}
self.println(
render_value(
bus,
&bb,
&pad,
settings,
max_variable_string_len,
LineStyle::Normal,
)
.join(" "),
LineItem::Bus(i),
)
.unwrap();
self.print_flattened_devices(&devices, settings);
if i + 1 != len {
self.println("", LineItem::None).unwrap();
}
}
}
}
pub fn mask_serial(device: &mut Device, hide: &MaskSerial, recursive: bool) {
if let Some(serial) = device.serial_num.as_mut() {
*serial = match hide {
MaskSerial::Hide => serial.chars().map(|_| '*').collect::<String>(),
MaskSerial::Scramble => serial
.chars()
.map(|_| {
serial
.chars()
.nth(fastrand::usize(0..serial.len()))
.unwrap_or('*')
})
.collect::<String>(),
MaskSerial::Replace => serial
.chars()
.map(|_| fastrand::alphanumeric())
.collect::<String>()
.to_uppercase(),
_ => serial.to_string(),
};
}
if recursive {
device
.devices
.iter_mut()
.for_each(|dd| dd.iter_mut().for_each(|d| mask_serial(d, hide, recursive)));
}
}
pub fn prepare(
sp_usb: &mut SystemProfile,
filter: Option<&DeviceFilter>,
settings: &PrintSettings,
) {
log::debug!("Running prepare pre-printing");
if !settings.tree && !matches!(settings.print_mode, PrintMode::Dynamic) {
log::debug!("Flattening SPUSBDataType");
sp_usb.into_flattened();
}
log::debug!("Filtering with {filter:?}");
if let Some(filter) = filter {
if matches!(settings.print_mode, PrintMode::Dynamic) {
filter.hide_buses(&mut sp_usb.buses);
} else {
filter.retain_buses(&mut sp_usb.buses);
}
}
log::debug!("Sorting with {:?}", settings.sort_devices);
settings.sort_devices.sort_buses(&mut sp_usb.buses);
if settings.sort_buses && matches!(settings.sort_devices, Sort::NoSort) {
log::debug!("Sorting buses by bus number");
sp_usb.buses.sort_by_key(|d| d.get_bus_number());
}
if let Some(hide) = settings.mask_serials.as_ref() {
log::debug!("Masking serials with {hide:?}");
for bus in &mut sp_usb.buses {
bus.devices.iter_mut().for_each(|devices| {
for device in devices {
mask_serial(device, hide, true);
}
});
}
}
log::trace!("sp_usb data post filter and bus sort\n\r{sp_usb:#}");
}
pub fn print(sp_usb: &SystemProfile, settings: &PrintSettings) {
log::trace!("Printing with {settings:?}");
let mut dw = DisplayWriter::default();
match settings.color_when {
ColorWhen::Always => colored::control::set_override(true),
ColorWhen::Never => colored::control::set_override(false),
ColorWhen::Auto => colored::control::unset_override(),
}
if settings.tree || settings.group_devices == Group::Bus {
if settings.json {
println!("{}", serde_json::to_string_pretty(&sp_usb).unwrap());
} else {
dw.print_sp_usb(sp_usb, settings);
}
} else {
let devs = sp_usb.flattened_devices();
if settings.json {
println!("{}", serde_json::to_string_pretty(&devs).unwrap());
} else {
dw.print_flattened_devices(&devs, settings);
}
}
}