whatcable 0.3.1

Tells you what each USB cable / device on Linux can actually do. Rust port of WhatCable.
Documentation
//! WhatCable CLI entry point.

use std::io::{self, Write};

use clap::Parser;

use whatcable::{DeviceManager, Sysfs};

mod output;

use output::{print_json, print_text};

#[derive(Parser, Debug)]
#[command(
    name = "whatcable",
    version,
    about = "Tells you what each USB cable / device on Linux can actually do.",
    long_about = "WhatCable — shows what each USB cable / device can do.\n\
                  Port of WhatCable (macOS) by Darryl Morley."
)]
struct Cli {
    /// Structured JSON output.
    #[arg(long)]
    json: bool,

    /// Stream updates as devices change (requires the `watch` feature).
    #[arg(long)]
    watch: bool,

    /// Include raw sysfs attributes in the output.
    #[arg(long)]
    raw: bool,

    /// Override the sysfs root (default: /sys). Useful for fixture-based testing.
    #[arg(long, value_name = "PATH")]
    sysfs_root: Option<std::path::PathBuf>,
}

fn main() -> io::Result<()> {
    let cli = Cli::parse();
    let sysfs = match cli.sysfs_root.as_ref() {
        Some(p) => Sysfs::with_root(p),
        None => Sysfs::linux(),
    };
    let mut mgr = DeviceManager::with_sysfs(sysfs);

    if cli.watch {
        #[cfg(feature = "watch")]
        return run_watch(&mut mgr, cli.json, cli.raw);
        #[cfg(not(feature = "watch"))]
        {
            eprintln!("--watch is not available: built without the `watch` feature.");
            std::process::exit(2);
        }
    }
    mgr.refresh();
    let stdout = io::stdout();
    let mut out = stdout.lock();
    if cli.json {
        print_json(&mut out, &mgr, cli.raw)
    } else {
        print_text(&mut out, &mgr, cli.raw)
    }
}

#[cfg(feature = "watch")]
fn run_watch(mgr: &mut DeviceManager, use_json: bool, show_raw: bool) -> io::Result<()> {
    use std::time::Duration;
    use whatcable::watch::{run_loop, RefreshReason};

    run_loop(Duration::from_millis(500), |reason| {
        mgr.refresh();
        let stdout = io::stdout();
        let mut out = stdout.lock();
        if !use_json {
            // Clear screen + home cursor between renders so the latest snapshot
            // is always visible at the top of the terminal.
            if matches!(reason, RefreshReason::Hotplug | RefreshReason::Initial) {
                write!(out, "\x1b[2J\x1b[H")?;
            }
            print_text(&mut out, mgr, show_raw)?;
        } else {
            print_json(&mut out, mgr, show_raw)?;
        }
        out.flush()
    })
}