aboutsummaryrefslogtreecommitdiff
path: root/src/main.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/main.rs')
-rw-r--r--src/main.rs161
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());
+ }
}