Skip to content

feat!: Better error reporting in hugr-cli. #2318

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jun 10, 2025
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
105 changes: 105 additions & 0 deletions Cargo.lock

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

4 changes: 4 additions & 0 deletions hugr-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ derive_more = { workspace = true, features = ["display", "error", "from"] }
hugr = { path = "../hugr", version = "0.20.1" }
serde_json.workspace = true
clio = { workspace = true, features = ["clap-parse"] }
anyhow.workspace = true
thiserror.workspace = true
tracing = "0.1.41"
tracing-subscriber = { version = "0.3.19", features = ["fmt"] }

[lints]
workspace = true
Expand Down
13 changes: 8 additions & 5 deletions hugr-cli/src/extensions.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
//! Dump standard extensions in serialized form.
use anyhow::Result;
use clap::Parser;
use hugr::extension::ExtensionRegistry;
use std::{io::Write, path::PathBuf};
Expand All @@ -25,7 +26,7 @@ impl ExtArgs {
/// Write out the standard extensions in serialized form.
/// Qualified names of extensions used to generate directories under the specified output directory.
/// E.g. extension "foo.bar.baz" will be written to "OUTPUT/foo/bar/baz.json".
pub fn run_dump(&self, registry: &ExtensionRegistry) {
pub fn run_dump(&self, registry: &ExtensionRegistry) -> Result<()> {
let base_dir = &self.outdir;

for ext in registry {
Expand All @@ -35,15 +36,17 @@ impl ExtArgs {
}
path.set_extension("json");

std::fs::create_dir_all(path.clone().parent().unwrap()).unwrap();
std::fs::create_dir_all(path.clone().parent().unwrap())?;
// file buffer
let mut file = std::fs::File::create(&path).unwrap();
let mut file = std::fs::File::create(&path)?;

serde_json::to_writer_pretty(&mut file, &ext).unwrap();
serde_json::to_writer_pretty(&mut file, &ext)?;

// write newline, for pre-commit end of file check that edits the file to
// add newlines if missing.
file.write_all(b"\n").unwrap();
file.write_all(b"\n")?;
}

Ok(())
}
}
51 changes: 23 additions & 28 deletions hugr-cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,11 @@
//! ```

use clap::{Parser, crate_version};
use clap_verbosity_flag::log::Level;
use clap_verbosity_flag::{InfoLevel, Verbosity};
use hugr::envelope::EnvelopeError;
use hugr::package::PackageValidationError;
use std::ffi::OsString;
use thiserror::Error;

pub mod extensions;
pub mod hugr_io;
Expand All @@ -73,8 +73,19 @@ pub mod validate;
#[clap(version = crate_version!(), long_about = None)]
#[clap(about = "HUGR CLI tools.")]
#[group(id = "hugr")]
pub struct CliArgs {
/// The command to be run.
#[command(subcommand)]
pub command: CliCommand,
/// Verbosity.
#[command(flatten)]
pub verbose: Verbosity<InfoLevel>,
}

/// The CLI subcommands.
#[derive(Debug, clap::Subcommand)]
#[non_exhaustive]
pub enum CliArgs {
pub enum CliCommand {
/// Validate and visualize a HUGR file.
Validate(validate::ValArgs),
/// Write standard extensions out in serialized form.
Expand All @@ -87,40 +98,24 @@ pub enum CliArgs {
}

/// Error type for the CLI.
#[derive(Debug, derive_more::Display, derive_more::Error, derive_more::From)]
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum CliError {
/// Error reading input.
#[display("Error reading from path: {_0}")]
InputFile(std::io::Error),
#[error("Error reading from path.")]
InputFile(#[from] std::io::Error),
/// Error parsing input.
#[display("Error parsing package: {_0}")]
Parse(serde_json::Error),
#[display("Error validating HUGR: {_0}")]
#[error("Error parsing package.")]
Parse(#[from] serde_json::Error),
#[error("Error validating HUGR.")]
/// Errors produced by the `validate` subcommand.
Validate(PackageValidationError),
#[display("Error decoding HUGR envelope: {_0}")]
Validate(#[from] PackageValidationError),
#[error("Error decoding HUGR envelope.")]
/// Errors produced by the `validate` subcommand.
Envelope(EnvelopeError),
Envelope(#[from] EnvelopeError),
/// Pretty error when the user passes a non-envelope file.
#[display(
#[error(
"Input file is not a HUGR envelope. Invalid magic number.\n\nUse `--hugr-json` to read a raw HUGR JSON file instead."
)]
NotAnEnvelope,
}

/// Other arguments affecting the HUGR CLI runtime.
#[derive(Parser, Debug)]
pub struct OtherArgs {
/// Verbosity.
#[command(flatten)]
pub verbose: Verbosity<InfoLevel>,
}

impl OtherArgs {
/// Test whether a `level` message should be output.
#[must_use]
pub fn verbosity(&self, level: Level) -> bool {
self.verbose.log_level_filter() >= level
}
}
Loading
Loading