#[derive(Debug, Clone, PartialEq, Default)]
pub struct MirrorArgs {
pub serial: Option<String>,
pub size: Option<(u32, u32)>,
pub title: Option<String>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Cli {
Gui,
Mirror(MirrorArgs),
Help,
}
pub const USAGE: &str = "\
DroidKraft — Android device monitor
USAGE:
droidkraft Open the full application
droidkraft --mirror [OPTIONS] Open only a screen-mirror window (scrcpy-like)
MIRROR OPTIONS:
--serial SERIAL Device to mirror (default: first connected device)
--size WxH Initial window size, e.g. 400x820
--title TITLE Window title
-h, --help Print this help
";
pub fn parse<I, S>(args: I) -> Result<Cli, String>
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
let mut mirror = false;
let mut m = MirrorArgs::default();
let mut it = args.into_iter().map(Into::into);
while let Some(a) = it.next() {
let (key, inline) = match a.split_once('=') {
Some((k, v)) if k.starts_with("--") => (k.to_string(), Some(v.to_string())),
_ => (a.clone(), None),
};
let mut value = |name: &str| -> Result<String, String> {
inline
.clone()
.or_else(|| it.next())
.ok_or_else(|| format!("{name} requires a value"))
};
match key.as_str() {
"-h" | "--help" => return Ok(Cli::Help),
"--mirror" => mirror = true,
"--serial" => m.serial = Some(value("--serial")?),
"--title" => m.title = Some(value("--title")?),
"--size" => m.size = Some(parse_size(&value("--size")?)?),
_ => return Err(format!("unknown argument: {a}")),
}
}
if !mirror && m != MirrorArgs::default() {
return Err("--serial/--size/--title require --mirror".into());
}
Ok(if mirror { Cli::Mirror(m) } else { Cli::Gui })
}
fn parse_size(s: &str) -> Result<(u32, u32), String> {
let bad = || format!("invalid --size '{s}', expected WxH (e.g. 400x820)");
let (w, h) = s.split_once(['x', 'X']).ok_or_else(bad)?;
let (w, h): (u32, u32) = (w.parse().map_err(|_| bad())?, h.parse().map_err(|_| bad())?);
if w < 100 || h < 100 {
return Err(bad());
}
Ok((w, h))
}
#[cfg(test)]
mod tests {
use super::*;
fn p(a: &[&str]) -> Result<Cli, String> {
parse(a.iter().copied())
}
#[test]
fn default_is_gui() {
assert_eq!(p(&[]), Ok(Cli::Gui));
}
#[test]
fn help() {
assert_eq!(p(&["--help"]), Ok(Cli::Help));
assert_eq!(p(&["--mirror", "-h"]), Ok(Cli::Help));
}
#[test]
fn mirror_full() {
assert_eq!(
p(&[
"--mirror",
"--serial",
"ABC",
"--size",
"400x820",
"--title=Hi"
]),
Ok(Cli::Mirror(MirrorArgs {
serial: Some("ABC".into()),
size: Some((400, 820)),
title: Some("Hi".into()),
}))
);
}
#[test]
fn mirror_bare() {
assert_eq!(p(&["--mirror"]), Ok(Cli::Mirror(MirrorArgs::default())));
}
#[test]
fn errors() {
assert!(p(&["--bogus"]).is_err());
assert!(p(&["--mirror", "--serial"]).is_err());
assert!(p(&["--mirror", "--size", "abc"]).is_err());
assert!(p(&["--mirror", "--size", "10x10"]).is_err());
assert!(p(&["--serial", "X"]).is_err());
}
#[test]
fn tui_detach_args_parse_to_mirror() {
assert_eq!(
p(&["--mirror", "--serial", "ABC", "--size", "800x600"]),
Ok(Cli::Mirror(MirrorArgs {
serial: Some("ABC".into()),
size: Some((800, 600)),
title: None,
}))
);
}
}