mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-25 10:17:02 -06:00
CLI: --help after a subcommand prints the usage
`vectorcraft-cli mcp --help` stopped with "unknown mcp option `--help`", and `run`, `convert`, `info`, `bench` and `perf` treated `--help` and `-h` as an unknown option or a file name, while `vectorcraft-cli help` printed the usage. `--help` or `-h` after a subcommand now prints the same usage and exits successfully, before the subcommand parses a value or reads a file, so `run --commands missing.json --help` prints it too. As the value of an option it stays that option's value: `run --cmd -h` and `convert IN --range -h` still read `-h` as before. The error for `--connect` with an automation root also had a run of 21 spaces in the middle of its text; it now has one.
This commit is contained in:
@@ -134,6 +134,29 @@ fn usage() -> String {
|
||||
format!("{USAGE}\nReadable formats: .{}\nWritable formats: .{}\n", OPEN_EXTS.join(", ."), export_extensions().join(", ."))
|
||||
}
|
||||
|
||||
/// Does a subcommand's `args` ask for the usage? `--help` or `-h` counts where an option goes, not as
|
||||
/// the value of one of the `valued` options before it, so `run --cmd -h` keeps `-h` for `--cmd`. The
|
||||
/// subcommands check this before they parse or read anything else.
|
||||
fn help_requested(args: &[String], valued: &[&str]) -> bool {
|
||||
let mut it = args.iter();
|
||||
while let Some(a) = it.next() {
|
||||
match a.as_str() {
|
||||
"--help" | "-h" => return true,
|
||||
a if valued.contains(&a) => {
|
||||
it.next();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Prints the usage, for `help`, `--help` and `-h`.
|
||||
fn help() -> Result<(), String> {
|
||||
out!("{}", usage());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() -> ExitCode {
|
||||
// Before any font lookup: the fonts font services load (#579).
|
||||
#[cfg(all(windows, not(target_vendor = "win7")))]
|
||||
@@ -143,6 +166,9 @@ fn main() -> ExitCode {
|
||||
prefs_dir::install_fonts();
|
||||
let args: Vec<String> = std::env::args().skip(1).collect();
|
||||
let r = match args.first().map(String::as_str) {
|
||||
// `commands` and `info` take no options, so `--help` or `-h` anywhere after them prints the
|
||||
// usage. The other subcommands check for it themselves.
|
||||
Some("commands" | "info") if help_requested(&args[1..], &[]) => help(),
|
||||
Some("mcp") => mcp(&args[1..]),
|
||||
Some("run") => run(&args[1..]),
|
||||
Some("commands") => commands(),
|
||||
@@ -150,10 +176,7 @@ fn main() -> ExitCode {
|
||||
Some("info") => info(&args[1..]),
|
||||
Some("bench") => bench(&args[1..]),
|
||||
Some("perf") => perf::run(&args[1..]),
|
||||
Some("-h" | "--help" | "help") | None => {
|
||||
out!("{}", usage());
|
||||
Ok(())
|
||||
}
|
||||
Some("-h" | "--help" | "help") | None => help(),
|
||||
Some("-V" | "--version") => {
|
||||
outln!("vectorcraft-cli {}", env!("CARGO_PKG_VERSION"));
|
||||
Ok(())
|
||||
@@ -180,6 +203,9 @@ fn with_stores(mut h: Headless) -> Headless {
|
||||
}
|
||||
|
||||
fn mcp(args: &[String]) -> Result<(), String> {
|
||||
if help_requested(args, &["--connect", "--automation-read-root", "--automation-write-root"]) {
|
||||
return help();
|
||||
}
|
||||
let mut connect: Option<String> = None;
|
||||
let mut headless = false;
|
||||
let (mut read_roots, mut write_roots): (Vec<String>, Vec<String>) = (vec![], vec![]);
|
||||
@@ -207,7 +233,7 @@ fn mcp(args: &[String]) -> Result<(), String> {
|
||||
let write: Vec<&Path> = write_roots.iter().map(Path::new).collect();
|
||||
let roots = AutomationRoots::new(&read, &write)?;
|
||||
if roots.is_some() && connect.is_some() {
|
||||
return Err("--automation-read-root and --automation-write-root confine the headless server; a running app enforces its own roots: start it with `vectorcraft --control <port> --automation-read-root <dir> --automation-write-root <dir>`"
|
||||
return Err("--automation-read-root and --automation-write-root confine the headless server; a running app enforces its own roots: start it with `vectorcraft --control <port> --automation-read-root <dir> --automation-write-root <dir>`"
|
||||
.into());
|
||||
}
|
||||
let backend: Box<dyn Backend> = if let Some(roots) = roots {
|
||||
@@ -243,6 +269,10 @@ fn commands() -> Result<(), String> {
|
||||
}
|
||||
|
||||
fn convert(args: &[String]) -> Result<(), String> {
|
||||
let valued = ["--scale", "-s", "--artboard", "-a", "--range", "-r", "--decimals", "--ai-version", "--hidden-objects", "--transparent-images"];
|
||||
if help_requested(args, &valued) {
|
||||
return help();
|
||||
}
|
||||
let mut files = vec![];
|
||||
let (mut scale, mut artboard, mut range, mut outline_text, mut replace_lossy, mut editing_data) =
|
||||
(1.0f64, None::<u64>, None::<String>, false, false, false);
|
||||
@@ -422,6 +452,10 @@ fn command_step(v: Value) -> Result<Step, String> {
|
||||
}
|
||||
|
||||
fn run(args: &[String]) -> Result<(), String> {
|
||||
let valued = ["--in", "-i", "--cmd", "-c", "--params", "-p", "--commands", "--export", "-o", "--scale", "-s"];
|
||||
if help_requested(args, &valued) {
|
||||
return help();
|
||||
}
|
||||
let mut input: Option<String> = None;
|
||||
let mut steps: Vec<Step> = vec![];
|
||||
// The step of the `--cmd` just given, which a `--params` sets.
|
||||
@@ -522,6 +556,9 @@ fn run(args: &[String]) -> Result<(), String> {
|
||||
}
|
||||
|
||||
fn bench(args: &[String]) -> Result<(), String> {
|
||||
if help_requested(args, &["--size", "--iters"]) {
|
||||
return help();
|
||||
}
|
||||
let mut file = None;
|
||||
let (mut w, mut h, mut iters) = (2880u32, 1800u32, 5u32);
|
||||
let mut it = args.iter();
|
||||
|
||||
@@ -341,6 +341,9 @@ fn deep(n: usize) -> Result<(), String> {
|
||||
}
|
||||
|
||||
pub fn run(args: &[String]) -> Result<(), String> {
|
||||
if crate::help_requested(args, &["--paths"]) {
|
||||
return crate::help();
|
||||
}
|
||||
let mut n = 50_000usize;
|
||||
let mut want_deep = false;
|
||||
let mut it = args.iter();
|
||||
|
||||
@@ -201,6 +201,64 @@ fn run_refuses_malformed_commands_naming_the_step() {
|
||||
assert_eq!(printed, 2);
|
||||
}
|
||||
|
||||
/// `--help` and `-h` right after a subcommand print the usage that `vectorcraft-cli help` prints.
|
||||
#[test]
|
||||
fn help_after_a_subcommand_prints_the_usage() {
|
||||
let usage = Command::new(BIN).arg("help").output().unwrap();
|
||||
assert!(usage.status.success() && String::from_utf8_lossy(&usage.stdout).contains("USAGE:"));
|
||||
for sub in ["mcp", "run", "commands", "convert", "info", "bench", "perf"] {
|
||||
for flag in ["--help", "-h"] {
|
||||
let out = Command::new(BIN).args([sub, flag]).stdin(Stdio::null()).output().unwrap();
|
||||
assert!(out.status.success(), "{sub} {flag}: {}", String::from_utf8_lossy(&out.stderr));
|
||||
assert!(out.stdout == usage.stdout, "{sub} {flag}: {}", String::from_utf8_lossy(&out.stdout));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `--help` and `-h` after other arguments print the usage too, before the subcommand does anything
|
||||
/// with them.
|
||||
#[test]
|
||||
fn help_later_in_the_line_prints_the_usage() {
|
||||
let usage = Command::new(BIN).arg("help").output().unwrap();
|
||||
let mut wrong = vec![];
|
||||
for args in [
|
||||
&["mcp", "--headless", "--help"][..],
|
||||
&["mcp", "--headless", "-h"],
|
||||
&["mcp", "--connect", "127.0.0.1:1", "--help"],
|
||||
&["run", "--cmd", "file.new", "--help"],
|
||||
&["run", "--in", "missing.svg", "-h"],
|
||||
&["run", "--commands", "missing.json", "--help"],
|
||||
&["run", "--cmd", "file.new", "--params", "not json", "-h"],
|
||||
&["commands", "x", "-h"],
|
||||
&["convert", "missing.svg", "out.png", "--help"],
|
||||
&["convert", "missing.svg", "--scale", "2", "-h"],
|
||||
&["info", "missing.svg", "--help"],
|
||||
&["bench", "missing.svg", "-h"],
|
||||
&["bench", "--size", "10x10", "--help"],
|
||||
&["bench", "--size", "bad", "-h"],
|
||||
&["perf", "--paths", "10", "--help"],
|
||||
&["perf", "--paths", "many", "-h"],
|
||||
&["convert", "--decimals", "many", "--help"],
|
||||
] {
|
||||
let out = Command::new(BIN).args(args).stdin(Stdio::null()).output().unwrap();
|
||||
if !out.status.success() || out.stdout != usage.stdout {
|
||||
wrong.push(format!("{args:?}: {}{}", String::from_utf8_lossy(&out.stderr), String::from_utf8_lossy(&out.stdout)));
|
||||
}
|
||||
}
|
||||
assert!(wrong.is_empty(), "{}", wrong.join("\n"));
|
||||
}
|
||||
|
||||
/// Further along the line, `-h` is the value of the option before it.
|
||||
#[test]
|
||||
fn help_as_an_option_value_goes_to_the_option() {
|
||||
let out = Command::new(BIN).args(["run", "--cmd", "-h"]).output().unwrap();
|
||||
let err = String::from_utf8_lossy(&out.stderr);
|
||||
assert!(!out.status.success() && err.contains("vectorcraft-cli: step 0 (-h):") && out.stdout.is_empty(), "{err}");
|
||||
let out = Command::new(BIN).args(["convert", "in.svg", "--range", "-h"]).output().unwrap();
|
||||
let err = String::from_utf8_lossy(&out.stderr);
|
||||
assert!(!out.status.success() && err.contains("convert needs IN and OUT") && out.stdout.is_empty(), "{err}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mcp_headless_over_stdio() {
|
||||
let mut child = Command::new(BIN).args(["mcp", "--headless"]).stdin(Stdio::piped()).stdout(Stdio::piped()).stderr(Stdio::null()).spawn().unwrap();
|
||||
@@ -319,7 +377,10 @@ fn automation_roots(base: &std::path::Path) {
|
||||
fails(&["mcp", "--automation-read-root", &dir("missing")], "cannot open the automation read root");
|
||||
fails(&["mcp", "--automation-read-root", &dir("in"), "--automation-read-root", &dir("missing")], "cannot open the automation read root");
|
||||
fails(&["mcp", "--automation-write-root"], "needs a folder");
|
||||
fails(&["mcp", "--connect", "127.0.0.1:1", &format!("--automation-write-root={}", dir("out"))], "a running app enforces its own roots");
|
||||
fails(
|
||||
&["mcp", "--connect", "127.0.0.1:1", &format!("--automation-write-root={}", dir("out"))],
|
||||
"a running app enforces its own roots: start it with `vectorcraft --control <port>",
|
||||
);
|
||||
|
||||
let mut child = Command::new(BIN)
|
||||
.args(["mcp", "--automation-read-root", &dir("in"), &format!("--automation-write-root={}", dir("out"))])
|
||||
|
||||
Reference in New Issue
Block a user