diff options
| author | jamie <git@skilet.ro> | 2026-08-29 22:15:55 +0100 |
|---|---|---|
| committer | jamie <git@skilet.ro> | 2026-08-29 22:15:55 +0100 |
| commit | 3dd652c1d70da608c77d1ded9e6d44774fc10ebc (patch) | |
| tree | cdd5d0ed985494f3ac66f19d4fedd8fd039b9357 /src/main.rs | |
| parent | c1110ac81f87ba374696e419f3807f48a3a06d6d (diff) | |
refactor: dedupe helpers, name protocol constants, testable daemon args
Diffstat (limited to 'src/main.rs')
| -rw-r--r-- | src/main.rs | 161 |
1 files changed, 114 insertions, 47 deletions
diff --git a/src/main.rs b/src/main.rs index 1a704fc..8582db1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,7 +12,6 @@ fn main() -> ExitCode { match args.first().map(String::as_str) { Some("status") => cmd_status(), Some("daemon") => cmd_daemon(&args[1..]), - Some("probe") => cmd_probe(), _ => { print_usage(); ExitCode::FAILURE @@ -46,14 +45,13 @@ fn cmd_status() -> ExitCode { eprintln!("dongle not found"); return ExitCode::from(2); }; - let hid = match Hidraw::open(&dev.devnode) { + let mut hid = match Hidraw::open(&dev.devnode) { Ok(h) => h, Err(e) => { eprintln!("error: {e}"); return ExitCode::from(2); } }; - let mut hid = hid; match protocol::link_status(&mut hid) { Ok(LinkState::Connected) => { println!("connected"); @@ -69,47 +67,17 @@ fn cmd_status() -> ExitCode { } } } - fn cmd_daemon(args: &[String]) -> ExitCode { - let mut on_connect: Option<String> = None; - let mut on_disconnect: Option<String> = None; - let mut interval = Duration::from_secs(2); - let mut no_initial = false; - - let mut it = args.iter(); - while let Some(arg) = it.next() { - match arg.as_str() { - "--on-connect" => match it.next() { - Some(c) => on_connect = Some(c.clone()), - None => { - eprintln!("--on-connect needs an argument"); - return ExitCode::FAILURE; - } - }, - "--on-disconnect" => match it.next() { - Some(c) => on_disconnect = Some(c.clone()), - None => { - eprintln!("--on-disconnect needs an argument"); - return ExitCode::FAILURE; - } - }, - "--interval" => match it.next().and_then(|s| s.parse::<u64>().ok()) { - Some(secs) if secs > 0 => interval = Duration::from_secs(secs), - _ => { - eprintln!("--interval needs a number of seconds > 0"); - return ExitCode::FAILURE; - } - }, - "--no-initial" => no_initial = true, - other => { - eprintln!("unknown option: {other}"); - return ExitCode::FAILURE; - } + let opts = match DaemonArgs::parse(args) { + Ok(o) => o, + Err(e) => { + eprintln!("{e}"); + return ExitCode::FAILURE; } - } + }; let mut hid: Option<Hidraw> = None; - let mut tracker = StateTracker::new(!no_initial); + let mut tracker = StateTracker::new(opts.fire_initial); let mut children: Vec<Child> = Vec::new(); loop { @@ -120,13 +88,13 @@ fn cmd_daemon(args: &[String]) -> ExitCode { match tracker.update(link_up) { Some(Transition::ToConnected { initial }) => { log_state("connected", initial); - if let Some(cmd) = &on_connect { + if let Some(cmd) = &opts.on_connect { spawn_hook(cmd, &mut children); } } Some(Transition::ToDisconnected { initial }) => { log_state("disconnected", initial); - if let Some(cmd) = &on_disconnect { + if let Some(cmd) = &opts.on_disconnect { spawn_hook(cmd, &mut children); } } @@ -134,8 +102,8 @@ fn cmd_daemon(args: &[String]) -> ExitCode { } let elapsed = cycle.elapsed(); - if elapsed < interval { - std::thread::sleep(interval - elapsed); + if elapsed < opts.interval { + std::thread::sleep(opts.interval - elapsed); } } } @@ -200,7 +168,106 @@ fn timestamp() -> u64 { .unwrap_or(0) } -fn cmd_probe() -> ExitCode { - eprintln!("use `mmx150-probe` binary instead"); - ExitCode::FAILURE +/// Parsed `mmx150 daemon` command line. +#[derive(Debug, PartialEq)] +struct DaemonArgs { + on_connect: Option<String>, + on_disconnect: Option<String>, + interval: Duration, + fire_initial: bool, +} + +impl DaemonArgs { + fn parse(args: &[String]) -> Result<DaemonArgs, String> { + let mut on_connect = None; + let mut on_disconnect = None; + let mut interval = Duration::from_secs(2); + let mut fire_initial = true; + + let mut it = args.iter(); + while let Some(arg) = it.next() { + match arg.as_str() { + "--on-connect" => { + on_connect = Some(it.next().ok_or("--on-connect needs an argument")?.clone()) + } + "--on-disconnect" => { + on_disconnect = Some( + it.next() + .ok_or("--on-disconnect needs an argument")? + .clone(), + ) + } + "--interval" => { + let secs = it + .next() + .and_then(|s| s.parse::<u64>().ok()) + .filter(|&s| s > 0) + .ok_or("--interval needs a number of seconds > 0")?; + interval = Duration::from_secs(secs); + } + "--no-initial" => fire_initial = false, + other => return Err(format!("unknown option: {other}")), + } + } + Ok(DaemonArgs { + on_connect, + on_disconnect, + interval, + fire_initial, + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn args(list: &[&str]) -> Vec<String> { + list.iter().map(|s| s.to_string()).collect() + } + + #[test] + fn daemon_args_defaults() { + let a = DaemonArgs::parse(&args(&[])).unwrap(); + assert_eq!(a.interval, Duration::from_secs(2)); + assert!(a.fire_initial); + assert_eq!(a.on_connect, None); + assert_eq!(a.on_disconnect, None); + } + + #[test] + fn daemon_args_full_parse() { + let a = DaemonArgs::parse(&args(&[ + "--on-connect", + "echo up", + "--on-disconnect", + "echo down", + "--interval", + "5", + "--no-initial", + ])) + .unwrap(); + assert_eq!(a.on_connect.as_deref(), Some("echo up")); + assert_eq!(a.on_disconnect.as_deref(), Some("echo down")); + assert_eq!(a.interval, Duration::from_secs(5)); + assert!(!a.fire_initial); + } + + #[test] + fn daemon_args_rejects_missing_option_argument() { + assert!(DaemonArgs::parse(&args(&["--on-connect"])).is_err()); + assert!(DaemonArgs::parse(&args(&["--on-disconnect"])).is_err()); + assert!(DaemonArgs::parse(&args(&["--interval"])).is_err()); + } + + #[test] + fn daemon_args_rejects_bad_interval() { + assert!(DaemonArgs::parse(&args(&["--interval", "0"])).is_err()); + assert!(DaemonArgs::parse(&args(&["--interval", "x"])).is_err()); + } + + #[test] + fn daemon_args_rejects_unknown_option() { + assert!(DaemonArgs::parse(&args(&["--wat"])).is_err()); + } } |
