use std::path::PathBuf;
use clap::{Parser, Subcommand};
const EXAMPLES: &str = "\
Examples:
pixelactions run --session DIR click:submit type:\"hi\" key:cmd+s wait:done --yes
pixelactions plan --flow flow.toml resolve every step, act on nothing
pixelactions run --flow flow.toml --yes
pixelactions serve --session DIR drive it from your own program
pixelactions doctor --probe prove input permission, harmlessly
Verbs, every one of them:
click:LABEL double:LABEL verify:LABEL
wait:LABEL gone:LABEL changed:LABEL
type:TEXT key:CHORD pause:MS
drag:FROM>TO scroll:LABEL>N hscroll:LABEL>N
They mirror the flow file's actions one-for-one, so learning either
teaches the other.
Actions reference a pixelcoords session by label, never by coordinate.
Exit codes: 0 done, 1 a step failed honestly, 2 malformed question,
3 refused (no --yes, kill switch tripped, permission missing,
unsupported platform).";
#[derive(Debug, Parser)]
#[command(name = "pixelactions", version, about, after_help = EXAMPLES)]
pub struct Cli {
#[command(subcommand)]
pub command: Command,
}
#[derive(Debug, Subcommand)]
pub enum Command {
Plan {
#[arg(long, conflicts_with = "session")]
flow: Option<PathBuf>,
#[arg(long, value_name = "DIR")]
session: Option<PathBuf>,
#[arg(value_name = "VERB:ARG")]
verbs: Vec<String>,
#[arg(long)]
json: bool,
#[arg(long, value_enum)]
space: Option<SpaceArg>,
},
Run {
#[arg(long, conflicts_with = "session")]
flow: Option<PathBuf>,
#[arg(long, value_name = "DIR")]
session: Option<PathBuf>,
#[arg(value_name = "VERB:ARG")]
verbs: Vec<String>,
#[arg(long)]
json: bool,
#[arg(long)]
yes: bool,
},
Mcp {
#[arg(long)]
yes: bool,
},
Serve {
#[arg(long, value_name = "DIR")]
session: PathBuf,
},
Doctor {
#[arg(long)]
json: bool,
#[arg(long)]
probe: bool,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)]
pub enum SpaceArg {
Auto,
Physical,
Logical,
}
impl From<SpaceArg> for pixelactions_core::convert::Space {
fn from(arg: SpaceArg) -> Self {
match arg {
SpaceArg::Auto => Self::Auto,
SpaceArg::Physical => Self::Physical,
SpaceArg::Logical => Self::Logical,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cli_definition_is_consistent() {
use clap::CommandFactory;
Cli::command().debug_assert();
}
#[test]
fn the_help_lists_every_verb_the_parser_accepts() {
let complaint = pixelactions_core::verb::VerbError::Unknown("x".into()).to_string();
let listed = complaint
.split("expected ")
.nth(1)
.expect("the error names the verbs");
let verbs: Vec<&str> = listed
.split(&[',', ' '][..])
.map(str::trim)
.filter(|word| !word.is_empty() && *word != "or")
.collect();
assert!(verbs.len() >= 12, "parsed too few verbs: {verbs:?}");
for verb in verbs {
assert!(
EXAMPLES.contains(verb),
"the parser accepts {verb:?} and the help does not mention it"
);
}
}
}