Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ serde_yaml = "0.9.34"
smol_str = "0.3.1"
static_assertions = "1.1.0"
strum = "0.27.0"
tempfile = "3.20"
thiserror = "2.0.12"
typetag = "0.2.20"
clap = { version = "4.5.38" }
Expand Down
1 change: 1 addition & 0 deletions hugr-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ struct_missing = "warn"
assert_cmd = { workspace = true }
assert_fs = { workspace = true }
predicates = { workspace = true }
tempfile = { workspace = true }
rstest.workspace = true

[[bin]]
Expand Down
35 changes: 29 additions & 6 deletions hugr-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,35 @@ fn main() {
CliArgs::Validate(args) => run_validate(args),
CliArgs::GenExtensions(args) => args.run_dump(&hugr::std_extensions::STD_REG),
CliArgs::Mermaid(args) => run_mermaid(args),
CliArgs::External(_) => {
// TODO: Implement support for external commands.
// Running `hugr COMMAND` would look for `hugr-COMMAND` in the path
// and run it.
eprintln!("External commands are not supported yet.");
std::process::exit(1);
CliArgs::External(args) => {
// External subcommand support: invoke `hugr-<subcommand>`
if args.is_empty() {
eprintln!("No external subcommand specified.");
std::process::exit(1);
}
let subcmd = args[0].to_string_lossy();
let exe = format!("hugr-{}", subcmd);
let rest: Vec<_> = args[1..]
.iter()
.map(|s| s.to_string_lossy().to_string())
.collect();
match std::process::Command::new(&exe).args(&rest).status() {
Ok(status) => {
if !status.success() {
std::process::exit(status.code().unwrap_or(1));
}
}
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
eprintln!(
"error: no such subcommand: '{subcmd}'.\nCould not find '{exe}' in PATH."
);
std::process::exit(1);
}
Err(e) => {
eprintln!("error: failed to invoke '{exe}': {e}");
std::process::exit(1);
}
}
}
_ => {
eprintln!("Unknown command");
Expand Down
45 changes: 45 additions & 0 deletions hugr-cli/tests/external.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
//! Tests for external subcommand support in hugr-cli.
#![cfg(all(test, not(miri)))]

use assert_cmd::Command;
use predicates::str::contains;
use std::env;
use std::fs;
#[cfg(unix)]
use std::os::unix::fs::PermissionsExt;
use tempfile::TempDir;

#[test]
fn test_missing_external_command() {
let mut cmd = Command::cargo_bin("hugr").unwrap();
cmd.arg("idontexist");
cmd.assert()
.failure()
.stderr(contains("no such subcommand"));
}

#[test]
#[cfg_attr(not(unix), ignore = "Dummy program supported on Unix-like systems")]
fn test_external_command_invocation() {
// Create a dummy external command in a temp dir
let tempdir = TempDir::new().unwrap();
let bin_path = tempdir.path().join("hugr-dummy");
fs::write(&bin_path, b"#!/bin/sh\necho dummy called: $@\nexit 42\n").unwrap();
let mut perms = fs::metadata(&bin_path).unwrap().permissions();
#[cfg(unix)]
perms.set_mode(0o755);
fs::set_permissions(&bin_path, perms).unwrap();

// Prepend tempdir to PATH
let orig_path = env::var("PATH").unwrap();
let new_path = format!("{}:{}", tempdir.path().display(), orig_path);
let mut cmd = Command::cargo_bin("hugr").unwrap();
cmd.env("PATH", new_path);
cmd.arg("dummy");
cmd.arg("foo");
cmd.arg("bar");
cmd.assert()
.failure()
.stdout(contains("dummy called: foo bar"))
.code(42);
}
5 changes: 4 additions & 1 deletion hugr-cli/tests/validate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,10 @@ fn test_doesnt_exist(mut val_cmd: Command) {
val_cmd
.assert()
.failure()
.stderr(contains("No such file or directory"));
// clap now prints something like:
// error: Invalid value for [INPUT]: Could not open "foobar": (os error 2)
// so just look for "Could not open"
.stderr(contains("Could not open"));
}

#[rstest]
Expand Down
Loading