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};
6use std::io::{Read, Write};
7
8use crate::CliError;
9use crate::hugr_io::HugrInputArgs;
10
11/// Convert between different HUGR envelope formats.
12#[derive(Parser, Debug)]
13#[clap(version = "1.0", long_about = None)]
14#[clap(about = "Convert a HUGR between different envelope formats.")]
15#[group(id = "hugr")]
16#[non_exhaustive]
17pub struct ConvertArgs {
18    /// Hugr input.
19    #[command(flatten)]
20    pub input_args: HugrInputArgs,
21
22    /// Output file. Use '-' for stdout.
23    #[clap(short, long, value_parser, default_value = "-")]
24    pub output: Output,
25
26    /// Output format. One of: json, model, model-exts, model-text, model-text-exts
27    #[clap(short, long, value_name = "FORMAT")]
28    pub format: Option<String>,
29
30    /// Use default text-based envelope configuration.
31    /// Cannot be combined with --format or --binary.
32    #[clap(long, conflicts_with_all = ["format", "binary"])]
33    pub text: bool,
34
35    /// Use default binary envelope configuration.
36    /// Cannot be combined with --format or --text.
37    #[clap(long, conflicts_with_all = ["format", "text"])]
38    pub binary: bool,
39
40    /// Enable zstd compression for the output
41    #[clap(long)]
42    pub compress: bool,
43
44    /// Zstd compression level (1-22, where 1 is fastest and 22 is best compression)
45    /// Uses the default level if not specified.
46    #[clap(long, value_name = "LEVEL", requires = "compress")]
47    pub compression_level: Option<u8>,
48}
49
50impl ConvertArgs {
51    /// Convert a HUGR between different envelope formats with optional input/output overrides.
52    ///
53    /// # Arguments
54    ///
55    /// * `input_override` - Optional reader to use instead of the CLI input argument.
56    /// * `output_override` - Optional writer to use instead of the CLI output argument.
57    pub fn run_convert_with_io<R: Read, W: Write>(
58        &mut self,
59        input_override: Option<R>,
60        mut output_override: Option<W>,
61    ) -> Result<()> {
62        let (env_config, package) = self
63            .input_args
64            .get_described_package_with_reader(input_override)?;
65
66        // Handle text and binary format flags, which override the format option
67        let mut config = if self.text {
68            EnvelopeConfig::text()
69        } else if self.binary {
70            EnvelopeConfig::binary()
71        } else {
72            // Parse the requested format
73            let format = match &self.format {
74                Some(fmt) => match fmt.as_str() {
75                    "json" => EnvelopeFormat::PackageJson,
76                    "model" => EnvelopeFormat::Model,
77                    "model-exts" => EnvelopeFormat::ModelWithExtensions,
78                    "model-text" => EnvelopeFormat::ModelText,
79                    "model-text-exts" => EnvelopeFormat::ModelTextWithExtensions,
80                    _ => Err(CliError::InvalidFormat(fmt.clone()))?,
81                },
82                None => env_config.header.config().format, // Use input format if not specified
83            };
84            EnvelopeConfig::new(format)
85        };
86
87        // Configure compression
88        if let Some(level) = self.compress.then_some(self.compression_level).flatten() {
89            config = config.with_zstd(ZstdConfig::new(level));
90        }
91
92        // Write the package with the requested format
93        if let Some(ref mut writer) = output_override {
94            hugr::envelope::write_envelope(writer, &package, config)?;
95        } else {
96            hugr::envelope::write_envelope(&mut self.output, &package, config)?;
97        }
98
99        Ok(())
100    }
101
102    /// Convert a HUGR between different envelope formats
103    pub fn run_convert(&mut self) -> Result<()> {
104        self.run_convert_with_io(None::<&[u8]>, None::<Vec<u8>>)
105    }
106}