Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
92d876d
feat: Add serial data types for SimpleReplacement and PersistentHugr …
lmondada Jun 4, 2025
c292185
chore(deps-rs): bump the patch group across 1 directory with 2 update…
dependabot[bot] Jun 5, 2025
71ee7b7
feat: Add MermaidFormatter to replace RenderConfig (#2275)
lmondada Jun 9, 2025
f51d048
fix: Fixed invalid extension name in test. (#2319)
zrho Jun 10, 2025
aee0e74
fix: Fixed bug in python model export name mangling. (#2323)
zrho Jun 10, 2025
273e81d
refactor(llvm): replace HashMap with BTreeMap (#2313)
ss2165 Jun 10, 2025
9d039d0
fix(py): correct ConstString JSON encoding (#2325)
ss2165 Jun 10, 2025
3b63b8a
feat(core): builder pattern for EnvelopeConfig (#2330)
ss2165 Jun 11, 2025
1cf16d2
fix: update CallGraph and remove_dead_funcs for module-only FuncDefns…
acl-cqc Jun 13, 2025
a660fc3
feat(cli): convert sub-command for converting envelope formats (#2331)
ss2165 Jun 13, 2025
d7c5d3f
feat(core, llvm): add array unpack operations (#2339)
ss2165 Jun 13, 2025
394af2f
fix: Export metadata in Python (#2342)
zrho Jun 16, 2025
e0dc98c
chore(deps-rs): bump the minor group across 1 directory with 3 update…
dependabot[bot] Jun 17, 2025
554022c
chore(deps-rs): bump the patch group across 1 directory with 4 update…
dependabot[bot] Jun 17, 2025
0060976
refactor(types.rs): rm incorrect comment and unnecessary allow-unused…
acl-cqc Jun 17, 2025
d195340
feat: Deprecate invalidation_set, add invalidated_nodes and SimpleRep…
acl-cqc Jun 18, 2025
939c082
feat: Rewrite for peeling a TailLoop (#2290)
acl-cqc Jun 18, 2025
507a255
feat: Create Module/FunctionBuilders from existing Hugrs (#2359)
aborgna-q Jun 23, 2025
67c83bb
docs: fix doc links in persistent
ss2165 Jun 24, 2025
5627050
feat: better errors using metadata from generator (#2368)
ss2165 Jun 25, 2025
0a91961
feat: use `core.` prefixes for generator metadata keys (#2371)
ss2165 Jun 25, 2025
59b013c
chore: release-plz update
ss2165 Jun 25, 2025
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
679 changes: 298 additions & 381 deletions Cargo.lock

Large diffs are not rendered by default.

17 changes: 9 additions & 8 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ result_large_err = "allow"
large_enum_variant = "allow"

[workspace.dependencies]
anyhow = "1.0.98"
insta = { version = "1.43.1" }
bitvec = "1.0.1"
capnp = "0.20.6"
Expand All @@ -57,40 +58,40 @@ jsonschema = "0.29.1"
lazy_static = "1.4.0"
num-rational = "0.4.1"
paste = "1.0"
proptest = "1.4.0"
proptest = "1.7.0"
proptest-derive = "0.5.0"
regex = "1.10.6"
regex-syntax = "0.8.3"
rstest = "0.24.0"
semver = "1.0.26"
serde = "1.0.219"
serde_json = "1.0.140"
serde_with = "3.12.0"
serde_with = "3.13.0"
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" }
clap = { version = "4.5.40" }
clio = "0.3.5"
clap-verbosity-flag = "3.0.1"
clap-verbosity-flag = "3.0.3"
assert_cmd = "2.0.17"
assert_fs = "1.1.3"
predicates = "3.1.0"
indexmap = "2.9.0"
fxhash = "0.2.1"
bumpalo = "3.16.0"
bumpalo = "3.18.1"
pathsearch = "0.2.0"
base64 = "0.22.1"
ordered-float = "5.0.0"
pest = "2.8.0"
pest_derive = "2.8.0"
pest = "2.8.1"
pest_derive = "2.8.1"
pretty = "0.12.4"
pretty_assertions = "1.4.1"
zstd = "0.13.2"
relrc = "0.4.1"
relrc = "0.4.6"

# These public dependencies usually require breaking changes downstream, so we
# try to be as permissive as possible.
Expand Down
6 changes: 6 additions & 0 deletions hugr-cli/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
# Changelog


## [0.20.2](https://github.com/CQCL/hugr/compare/hugr-cli-v0.20.1...hugr-cli-v0.20.2) - 2025-06-25

### New Features

- *(cli)* convert sub-command for converting envelope formats ([#2331](https://github.com/CQCL/hugr/pull/2331))

## [0.20.1](https://github.com/CQCL/hugr/compare/hugr-cli-v0.20.0...hugr-cli-v0.20.1) - 2025-06-03

### New Features
Expand Down
6 changes: 4 additions & 2 deletions hugr-cli/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "hugr-cli"
version = "0.20.1"
version = "0.20.2"
edition = { workspace = true }
rust-version = { workspace = true }
license = { workspace = true }
Expand All @@ -19,9 +19,11 @@ bench = false
clap = { workspace = true, features = ["derive", "cargo"] }
clap-verbosity-flag.workspace = true
derive_more = { workspace = true, features = ["display", "error", "from"] }
hugr = { path = "../hugr", version = "0.20.1" }
hugr = { path = "../hugr", version = "0.20.2" }
serde_json.workspace = true
clio = { workspace = true, features = ["clap-parse"] }
anyhow.workspace = true
thiserror.workspace = true

[lints]
workspace = true
Expand Down
85 changes: 85 additions & 0 deletions hugr-cli/src/convert.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
//! Convert between different HUGR envelope formats.
use anyhow::Result;
use clap::Parser;
use clio::Output;
use hugr::envelope::{EnvelopeConfig, EnvelopeFormat, ZstdConfig};

use crate::CliError;
use crate::hugr_io::HugrInputArgs;

/// Convert between different HUGR envelope formats.
#[derive(Parser, Debug)]
#[clap(version = "1.0", long_about = None)]
#[clap(about = "Convert a HUGR between different envelope formats.")]
#[group(id = "hugr")]
#[non_exhaustive]
pub struct ConvertArgs {
/// Hugr input.
#[command(flatten)]
pub input_args: HugrInputArgs,

/// Output file. Use '-' for stdout.
#[clap(short, long, value_parser, default_value = "-")]
pub output: Output,

/// Output format. One of: json, model, model-exts, model-text, model-text-exts
#[clap(short, long, value_name = "FORMAT")]
pub format: Option<String>,

/// Use default text-based envelope configuration.
/// Cannot be combined with --format or --binary.
#[clap(long, conflicts_with_all = ["format", "binary"])]
pub text: bool,

/// Use default binary envelope configuration.
/// Cannot be combined with --format or --text.
#[clap(long, conflicts_with_all = ["format", "text"])]
pub binary: bool,

/// Enable zstd compression for the output
#[clap(long)]
pub compress: bool,

/// Zstd compression level (1-22, where 1 is fastest and 22 is best compression)
/// Uses the default level if not specified.
#[clap(long, value_name = "LEVEL", requires = "compress")]
pub compression_level: Option<u8>,
}

impl ConvertArgs {
/// Convert a HUGR between different envelope formats
pub fn run_convert(&mut self) -> Result<()> {
let (env_config, package) = self.input_args.get_envelope()?;

// Handle text and binary format flags, which override the format option
let mut config = if self.text {
EnvelopeConfig::text()
} else if self.binary {
EnvelopeConfig::binary()
} else {
// Parse the requested format
let format = match &self.format {
Some(fmt) => match fmt.as_str() {
"json" => EnvelopeFormat::PackageJson,
"model" => EnvelopeFormat::Model,
"model-exts" => EnvelopeFormat::ModelWithExtensions,
"model-text" => EnvelopeFormat::ModelText,
"model-text-exts" => EnvelopeFormat::ModelTextWithExtensions,
_ => Err(CliError::InvalidFormat(fmt.clone()))?,
},
None => env_config.format, // Use input format if not specified
};
EnvelopeConfig::new(format)
};

// Configure compression
if let Some(level) = self.compress.then_some(self.compression_level).flatten() {
config = config.with_zstd(ZstdConfig::new(level));
}

// Write the package with the requested format
hugr::envelope::write_envelope(&mut self.output, &package, config)?;

Ok(())
}
}
25 changes: 18 additions & 7 deletions hugr-cli/src/hugr_io.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Input/output arguments for the HUGR CLI.

use clio::Input;
use hugr::envelope::{EnvelopeError, read_envelope};
use hugr::envelope::{EnvelopeConfig, EnvelopeError, read_envelope};
use hugr::extension::ExtensionRegistry;
use hugr::package::Package;
use hugr::{Extension, Hugr};
Expand Down Expand Up @@ -43,18 +43,29 @@ impl HugrInputArgs {
/// Read a hugr envelope from the input and return the package encoded
/// within.
///
/// # Errors
///
/// If [`HugrInputArgs::hugr_json`] is `true`, [`HugrInputArgs::get_hugr`] should be called instead as
/// reading the input as a package will fail.
pub fn get_package(&mut self) -> Result<Package, CliError> {
self.get_envelope().map(|(_, package)| package)
}

/// Read a hugr envelope from the input and return the envelope
/// configuration and the package encoded within.
///
/// # Errors
///
/// If [`HugrInputArgs::hugr_json`] is `true`, [`HugrInputArgs::get_hugr`] should be called instead as
/// reading the input as a package will fail.
pub fn get_envelope(&mut self) -> Result<(EnvelopeConfig, Package), CliError> {
let extensions = self.load_extensions()?;
let buffer = BufReader::new(&mut self.input);
match read_envelope(buffer, &extensions) {
Ok((_, pkg)) => Ok(pkg),
Err(EnvelopeError::MagicNumber { .. }) => Err(CliError::NotAnEnvelope),
Err(e) => Err(CliError::Envelope(e)),
}
read_envelope(buffer, &extensions).map_err(|e| match e {
EnvelopeError::MagicNumber { .. } => CliError::NotAnEnvelope,
_ => CliError::Envelope(e),
})
}

/// Read a hugr JSON file from the input.
///
/// This is a legacy option for reading old HUGR JSON files when the
Expand Down
10 changes: 9 additions & 1 deletion hugr-cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ use hugr::envelope::EnvelopeError;
use hugr::package::PackageValidationError;
use std::ffi::OsString;

pub mod convert;
pub mod extensions;
pub mod hugr_io;
pub mod mermaid;
Expand All @@ -81,13 +82,15 @@ pub enum CliArgs {
GenExtensions(extensions::ExtArgs),
/// Write HUGR as mermaid diagrams.
Mermaid(mermaid::MermaidArgs),
/// Convert between different HUGR envelope formats.
Convert(convert::ConvertArgs),
/// External commands
#[command(external_subcommand)]
External(Vec<OsString>),
}

/// Error type for the CLI.
#[derive(Debug, derive_more::Display, derive_more::Error, derive_more::From)]
#[derive(Debug, derive_more::Display, thiserror::Error, derive_more::From)]
#[non_exhaustive]
pub enum CliError {
/// Error reading input.
Expand All @@ -107,6 +110,11 @@ pub enum CliError {
"Input file is not a HUGR envelope. Invalid magic number.\n\nUse `--hugr-json` to read a raw HUGR JSON file instead."
)]
NotAnEnvelope,
/// Invalid format string for conversion.
#[display(
"Invalid format: '{_0}'. Valid formats are: json, model, model-exts, model-text, model-text-exts"
)]
InvalidFormat(String),
}

/// Other arguments affecting the HUGR CLI runtime.
Expand Down
13 changes: 12 additions & 1 deletion hugr-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

use clap::Parser as _;

use hugr_cli::{CliArgs, mermaid, validate};
use hugr_cli::{CliArgs, convert, mermaid, validate};

use clap_verbosity_flag::log::Level;

Expand All @@ -11,6 +11,7 @@ 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::Convert(args) => run_convert(args),
CliArgs::External(args) => {
// External subcommand support: invoke `hugr-<subcommand>`
if args.is_empty() {
Expand Down Expand Up @@ -71,3 +72,13 @@ fn run_mermaid(mut args: mermaid::MermaidArgs) {
std::process::exit(1);
}
}

/// Run the `convert` subcommand.
fn run_convert(mut args: convert::ConvertArgs) {
let result = args.run_convert();

if let Err(e) = result {
eprintln!("{e}");
std::process::exit(1);
}
}
Loading
Loading