hugr_cli/
hugr_io.rs

1//! Input/output arguments for the HUGR CLI.
2
3use clio::Input;
4use hugr::envelope::{EnvelopeConfig, EnvelopeError, read_envelope};
5use hugr::extension::ExtensionRegistry;
6use hugr::package::Package;
7use hugr::{Extension, Hugr};
8use std::io::{BufReader, Read};
9use std::path::PathBuf;
10
11use crate::CliError;
12
13/// Arguments for reading a HUGR input.
14#[derive(Debug, clap::Args)]
15pub struct HugrInputArgs {
16    /// Input file. Defaults to `-` for stdin.
17    #[arg(value_parser, default_value = "-", help_heading = "Input")]
18    pub input: Input,
19
20    /// No standard extensions.
21    #[arg(
22        long,
23        help_heading = "Input",
24        help = "Don't use standard extensions when validating hugrs. Prelude is still used."
25    )]
26    pub no_std: bool,
27    /// Extensions paths.
28    #[arg(
29        short,
30        long,
31        help_heading = "Input",
32        help = "Paths to serialised extensions to validate against."
33    )]
34    pub extensions: Vec<PathBuf>,
35    /// Read the input as a HUGR JSON file instead of an envelope.
36    ///
37    /// This is a legacy option for reading old HUGR files.
38    #[clap(long, help_heading = "Input")]
39    pub hugr_json: bool,
40}
41
42impl HugrInputArgs {
43    /// Read a hugr envelope from the input and return the package encoded
44    /// within.
45    ///
46    /// # Errors
47    ///
48    /// If [`HugrInputArgs::hugr_json`] is `true`, [`HugrInputArgs::get_hugr`] should be called instead as
49    /// reading the input as a package will fail.
50    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> {
62        let extensions = self.load_extensions()?;
63        let buffer = BufReader::new(&mut self.input);
64        read_envelope(buffer, &extensions).map_err(|e| match e {
65            EnvelopeError::MagicNumber { .. } => CliError::NotAnEnvelope,
66            _ => CliError::Envelope(e),
67        })
68    }
69    /// Read a hugr JSON file from the input.
70    ///
71    /// This is a legacy option for reading old HUGR JSON files when the
72    /// [`HugrInputArgs::hugr_json`] flag is used.
73    ///
74    /// For most cases, [`HugrInputArgs::get_package`] should be called instead.
75    #[deprecated(note = "Use `HugrInputArgs::get_package` instead.", since = "0.22.2")]
76    pub fn get_hugr(&mut self) -> Result<Hugr, CliError> {
77        let extensions = self.load_extensions()?;
78        let mut buffer = BufReader::new(&mut self.input);
79
80        /// Wraps the hugr JSON so that it defines a valid envelope.
81        const PREPEND: &str = r#"HUGRiHJv?@{"modules": ["#;
82        const APPEND: &str = r#"],"extensions": []}"#;
83
84        let mut envelope = PREPEND.to_string();
85        buffer.read_to_string(&mut envelope)?;
86        envelope.push_str(APPEND);
87
88        let hugr = Hugr::load_str(envelope, Some(&extensions))?;
89        Ok(hugr)
90    }
91
92    /// Return a register with the selected extensions.
93    ///
94    /// This includes the standard extensions if [`HugrInputArgs::no_std`] is `false`,
95    /// and the extensions loaded from the paths in [`HugrInputArgs::extensions`].
96    pub fn load_extensions(&self) -> Result<ExtensionRegistry, CliError> {
97        let mut reg = if self.no_std {
98            hugr::extension::PRELUDE_REGISTRY.to_owned()
99        } else {
100            hugr::std_extensions::STD_REG.to_owned()
101        };
102
103        for ext in &self.extensions {
104            let f = std::fs::File::open(ext)?;
105            let ext: Extension = serde_json::from_reader(f)?;
106            reg.register_updated(ext);
107        }
108
109        Ok(reg)
110    }
111}