Skip to content

Commit 4dcf9e1

Browse files
committed
Merge remote-tracking branch 'origin/main' into acl/linking_prelims
2 parents c5f4363 + d79b738 commit 4dcf9e1

27 files changed

+1853
-1146
lines changed

hugr-cli/src/convert.rs

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
//! Convert between different HUGR envelope formats.
2+
use anyhow::Result;
3+
use clap::Parser;
4+
use clio::Output;
5+
use hugr::envelope::{EnvelopeConfig, EnvelopeFormat, ZstdConfig};
6+
7+
use crate::CliError;
8+
use crate::hugr_io::HugrInputArgs;
9+
10+
/// Convert between different HUGR envelope formats.
11+
#[derive(Parser, Debug)]
12+
#[clap(version = "1.0", long_about = None)]
13+
#[clap(about = "Convert a HUGR between different envelope formats.")]
14+
#[group(id = "hugr")]
15+
#[non_exhaustive]
16+
pub struct ConvertArgs {
17+
/// Hugr input.
18+
#[command(flatten)]
19+
pub input_args: HugrInputArgs,
20+
21+
/// Output file. Use '-' for stdout.
22+
#[clap(short, long, value_parser, default_value = "-")]
23+
pub output: Output,
24+
25+
/// Output format. One of: json, model, model-exts, model-text, model-text-exts
26+
#[clap(short, long, value_name = "FORMAT")]
27+
pub format: Option<String>,
28+
29+
/// Use default text-based envelope configuration.
30+
/// Cannot be combined with --format or --binary.
31+
#[clap(long, conflicts_with_all = ["format", "binary"])]
32+
pub text: bool,
33+
34+
/// Use default binary envelope configuration.
35+
/// Cannot be combined with --format or --text.
36+
#[clap(long, conflicts_with_all = ["format", "text"])]
37+
pub binary: bool,
38+
39+
/// Enable zstd compression for the output
40+
#[clap(long)]
41+
pub compress: bool,
42+
43+
/// Zstd compression level (1-22, where 1 is fastest and 22 is best compression)
44+
/// Uses the default level if not specified.
45+
#[clap(long, value_name = "LEVEL", requires = "compress")]
46+
pub compression_level: Option<u8>,
47+
}
48+
49+
impl ConvertArgs {
50+
/// Convert a HUGR between different envelope formats
51+
pub fn run_convert(&mut self) -> Result<()> {
52+
let (env_config, package) = self.input_args.get_envelope()?;
53+
54+
// Handle text and binary format flags, which override the format option
55+
let mut config = if self.text {
56+
EnvelopeConfig::text()
57+
} else if self.binary {
58+
EnvelopeConfig::binary()
59+
} else {
60+
// Parse the requested format
61+
let format = match &self.format {
62+
Some(fmt) => match fmt.as_str() {
63+
"json" => EnvelopeFormat::PackageJson,
64+
"model" => EnvelopeFormat::Model,
65+
"model-exts" => EnvelopeFormat::ModelWithExtensions,
66+
"model-text" => EnvelopeFormat::ModelText,
67+
"model-text-exts" => EnvelopeFormat::ModelTextWithExtensions,
68+
_ => Err(CliError::InvalidFormat(fmt.clone()))?,
69+
},
70+
None => env_config.format, // Use input format if not specified
71+
};
72+
EnvelopeConfig::new(format)
73+
};
74+
75+
// Configure compression
76+
if let Some(level) = self.compress.then_some(self.compression_level).flatten() {
77+
config = config.with_zstd(ZstdConfig::new(level));
78+
}
79+
80+
// Write the package with the requested format
81+
hugr::envelope::write_envelope(&mut self.output, &package, config)?;
82+
83+
Ok(())
84+
}
85+
}

hugr-cli/src/hugr_io.rs

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! Input/output arguments for the HUGR CLI.
22
33
use clio::Input;
4-
use hugr::envelope::{EnvelopeError, read_envelope};
4+
use hugr::envelope::{EnvelopeConfig, EnvelopeError, read_envelope};
55
use hugr::extension::ExtensionRegistry;
66
use hugr::package::Package;
77
use hugr::{Extension, Hugr};
@@ -43,18 +43,29 @@ impl HugrInputArgs {
4343
/// Read a hugr envelope from the input and return the package encoded
4444
/// within.
4545
///
46+
/// # Errors
47+
///
4648
/// If [`HugrInputArgs::hugr_json`] is `true`, [`HugrInputArgs::get_hugr`] should be called instead as
4749
/// reading the input as a package will fail.
4850
pub fn get_package(&mut self) -> Result<Package, CliError> {
51+
self.get_envelope().map(|(_, package)| package)
52+
}
53+
54+
/// Read a hugr envelope from the input and return the envelope
55+
/// configuration and the package encoded within.
56+
///
57+
/// # Errors
58+
///
59+
/// If [`HugrInputArgs::hugr_json`] is `true`, [`HugrInputArgs::get_hugr`] should be called instead as
60+
/// reading the input as a package will fail.
61+
pub fn get_envelope(&mut self) -> Result<(EnvelopeConfig, Package), CliError> {
4962
let extensions = self.load_extensions()?;
5063
let buffer = BufReader::new(&mut self.input);
51-
match read_envelope(buffer, &extensions) {
52-
Ok((_, pkg)) => Ok(pkg),
53-
Err(EnvelopeError::MagicNumber { .. }) => Err(CliError::NotAnEnvelope),
54-
Err(e) => Err(CliError::Envelope(e)),
55-
}
64+
read_envelope(buffer, &extensions).map_err(|e| match e {
65+
EnvelopeError::MagicNumber { .. } => CliError::NotAnEnvelope,
66+
_ => CliError::Envelope(e),
67+
})
5668
}
57-
5869
/// Read a hugr JSON file from the input.
5970
///
6071
/// This is a legacy option for reading old HUGR JSON files when the

hugr-cli/src/lib.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ use hugr::package::PackageValidationError;
6363
use std::ffi::OsString;
6464
use thiserror::Error;
6565

66+
pub mod convert;
6667
pub mod extensions;
6768
pub mod hugr_io;
6869
pub mod mermaid;
@@ -92,6 +93,8 @@ pub enum CliCommand {
9293
GenExtensions(extensions::ExtArgs),
9394
/// Write HUGR as mermaid diagrams.
9495
Mermaid(mermaid::MermaidArgs),
96+
/// Convert between different HUGR envelope formats.
97+
Convert(convert::ConvertArgs),
9598
/// External commands
9699
#[command(external_subcommand)]
97100
External(Vec<OsString>),
@@ -118,4 +121,9 @@ pub enum CliError {
118121
"Input file is not a HUGR envelope. Invalid magic number.\n\nUse `--hugr-json` to read a raw HUGR JSON file instead."
119122
)]
120123
NotAnEnvelope,
124+
/// Invalid format string for conversion.
125+
#[error(
126+
"Invalid format: '{_0}'. Valid formats are: json, model, model-exts, model-text, model-text-exts"
127+
)]
128+
InvalidFormat(String),
121129
}

hugr-cli/src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ fn main() {
2929
CliCommand::Validate(mut args) => args.run(),
3030
CliCommand::GenExtensions(args) => args.run_dump(&hugr::std_extensions::STD_REG),
3131
CliCommand::Mermaid(mut args) => args.run_print(),
32+
CliCommand::Convert(mut args) => args.run_convert(),
3233
CliCommand::External(args) => run_external(args),
3334
_ => Err(anyhow!("Unknown command")),
3435
};

0 commit comments

Comments
 (0)