use crate::{App, Error, Result};
pub fn run<A: App>() -> Result {
let args: Vec<String> = std::env::args().collect();
match parse(&args)? {
Action::Help => {
print_help();
Ok(())
}
Action::Script(path) => crate::automation::cli::script::<A>(&path),
Action::Replay(path) => crate::automation::cli::replay::<A>(&path),
Action::Inspect => {
let snapshot = crate::automation::cli::inspect::<A>()?;
println!("{snapshot}");
Ok(())
}
Action::Fallthrough => crate::run::<A>(),
}
}
#[derive(Debug)]
enum Action {
Fallthrough,
Help,
Script(String),
Replay(String),
Inspect,
}
fn known_plushie_flag(flag: &str) -> bool {
matches!(
flag,
"--plushie-mode"
| "--plushie-socket"
| "--plushie-token"
| "--plushie-script"
| "--plushie-replay"
| "--plushie-inspect"
| "--plushie-help"
)
}
fn parse(args: &[String]) -> std::result::Result<Action, Error> {
let mut i = 1;
let mut action = Action::Fallthrough;
while i < args.len() {
let arg = &args[i];
if !arg.starts_with("--plushie-") {
i += 1;
continue;
}
let (flag, inline_value) = match arg.split_once('=') {
Some((f, v)) => (f, Some(v.to_string())),
None => (arg.as_str(), None),
};
if !known_plushie_flag(flag) {
return Err(Error::InvalidSettings(format!(
"unknown flag `{flag}`; run with --plushie-help for the \
reserved-flag list"
)));
}
match flag {
"--plushie-help" => {
action = Action::Help;
break;
}
"--plushie-inspect" => {
action = Action::Inspect;
break;
}
"--plushie-script" => {
let value = take_value(flag, inline_value, args, &mut i)?;
action = Action::Script(value);
break;
}
"--plushie-replay" => {
let value = take_value(flag, inline_value, args, &mut i)?;
action = Action::Replay(value);
break;
}
"--plushie-mode" | "--plushie-socket" | "--plushie-token" => {
if inline_value.is_none() {
i += 1; }
}
_ => unreachable!("known_plushie_flag lies"),
}
i += 1;
}
Ok(action)
}
fn take_value(
flag: &str,
inline: Option<String>,
args: &[String],
i: &mut usize,
) -> std::result::Result<String, Error> {
if let Some(v) = inline {
Ok(v)
} else {
*i += 1;
args.get(*i).cloned().ok_or_else(|| {
Error::InvalidSettings(format!("{flag} requires a value (e.g. `{flag} <path>`)"))
})
}
}
fn print_help() {
println!(
"plushie reserved CLI flags:
--plushie-help Print this help text.
--plushie-mode=<mode> Force `direct` or `wire` runner selection.
--plushie-socket <path> Attach to a listen-mode renderer socket.
--plushie-token <token> Token presented during socket handshake.
--plushie-script <path> Execute a .plushie automation script.
--plushie-replay <path> Replay a .plushie script against a real renderer.
--plushie-inspect Print a pretty-JSON snapshot of the initial view tree.
Flags without the `--plushie-` prefix are ignored by this entry point,
so you can still build your own CLI on top (or skip this module entirely
and call plushie::run / plushie::automation::cli directly)."
);
}
#[cfg(test)]
mod tests {
use super::*;
fn parse_args(args: &[&str]) -> std::result::Result<Action, Error> {
let owned: Vec<String> = args.iter().map(|s| (*s).to_string()).collect();
parse(&owned)
}
#[test]
fn no_flags_falls_through() {
let action = parse_args(&["app"]).unwrap();
assert!(matches!(action, Action::Fallthrough));
}
#[test]
fn help_flag_recognised() {
let action = parse_args(&["app", "--plushie-help"]).unwrap();
assert!(matches!(action, Action::Help));
}
#[test]
fn inspect_flag_recognised() {
let action = parse_args(&["app", "--plushie-inspect"]).unwrap();
assert!(matches!(action, Action::Inspect));
}
#[test]
fn script_with_separate_value() {
let action = parse_args(&["app", "--plushie-script", "foo.plushie"]).unwrap();
assert!(matches!(action, Action::Script(ref p) if p == "foo.plushie"));
}
#[test]
fn script_with_equals_value() {
let action = parse_args(&["app", "--plushie-script=bar.plushie"]).unwrap();
assert!(matches!(action, Action::Script(ref p) if p == "bar.plushie"));
}
#[test]
fn replay_with_separate_value() {
let action = parse_args(&["app", "--plushie-replay", "x.plushie"]).unwrap();
assert!(matches!(action, Action::Replay(ref p) if p == "x.plushie"));
}
#[test]
fn unknown_plushie_flag_errors() {
let err = parse_args(&["app", "--plushie-wombat"]).unwrap_err();
let msg = format!("{err}");
assert!(msg.contains("unknown flag"), "got: {msg}");
assert!(msg.contains("--plushie-help"), "got: {msg}");
}
#[test]
fn script_missing_value_errors() {
let err = parse_args(&["app", "--plushie-script"]).unwrap_err();
let msg = format!("{err}");
assert!(msg.contains("requires a value"), "got: {msg}");
}
#[test]
fn non_plushie_flags_ignored() {
let action = parse_args(&["app", "--verbose", "--count=3"]).unwrap();
assert!(matches!(action, Action::Fallthrough));
}
#[test]
fn mode_flag_passthrough_with_separate_value() {
let action = parse_args(&["app", "--plushie-mode", "direct"]).unwrap();
assert!(matches!(action, Action::Fallthrough));
}
#[test]
fn mode_flag_passthrough_with_equals_value() {
let action = parse_args(&["app", "--plushie-mode=wire"]).unwrap();
assert!(matches!(action, Action::Fallthrough));
}
#[test]
fn socket_flag_passthrough() {
let action = parse_args(&["app", "--plushie-socket", "/tmp/sock"]).unwrap();
assert!(matches!(action, Action::Fallthrough));
}
#[test]
fn inspect_wins_over_later_flags() {
let action = parse_args(&["app", "--plushie-inspect", "--plushie-wombat"]).unwrap();
assert!(matches!(action, Action::Inspect));
}
}