hugr_cli/
hugr_io.rs

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