hugr_cli/
convert.rs

1//! Convert between different HUGR envelope formats.
2use anyhow::Result;
3use clap::Parser;
4use clio::Output;
5use hugr::envelope::{EnvelopeConfig, EnvelopeFormat, ZstdConfig};
6
7use crate::CliError;
8use 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]
16pub 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
49impl 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_described_package()?;
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.header.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}