hugr_cli/
lib.rs

1//! Standard command line tools, used by the hugr binary.
2
3use clap::{crate_version, Parser};
4use clap_verbosity_flag::{InfoLevel, Verbosity};
5use clio::Input;
6use derive_more::{Display, Error, From};
7use hugr::extension::ExtensionRegistry;
8use hugr::package::{PackageEncodingError, PackageValidationError};
9use hugr::Hugr;
10use std::io::{Cursor, Read, Seek, SeekFrom};
11use std::{ffi::OsString, path::PathBuf};
12
13pub mod extensions;
14pub mod mermaid;
15pub mod validate;
16
17use hugr::package::Package;
18
19/// CLI arguments.
20#[derive(Parser, Debug)]
21#[clap(version = crate_version!(), long_about = None)]
22#[clap(about = "HUGR CLI tools.")]
23#[group(id = "hugr")]
24#[non_exhaustive]
25pub enum CliArgs {
26    /// Validate and visualize a HUGR file.
27    Validate(validate::ValArgs),
28    /// Write standard extensions out in serialized form.
29    GenExtensions(extensions::ExtArgs),
30    /// Write HUGR as mermaid diagrams.
31    Mermaid(mermaid::MermaidArgs),
32    /// External commands
33    #[command(external_subcommand)]
34    External(Vec<OsString>),
35}
36
37/// Error type for the CLI.
38#[derive(Debug, Display, Error, From)]
39#[non_exhaustive]
40pub enum CliError {
41    /// Error reading input.
42    #[display("Error reading from path: {_0}")]
43    InputFile(std::io::Error),
44    /// Error parsing input.
45    #[display("Error parsing package: {_0}")]
46    Parse(serde_json::Error),
47    /// Hugr load error.
48    #[display("Error parsing package: {_0}")]
49    HUGRLoad(PackageEncodingError),
50    #[display("Error validating HUGR: {_0}")]
51    /// Errors produced by the `validate` subcommand.
52    Validate(PackageValidationError),
53}
54
55/// Validate and visualise a HUGR file.
56#[derive(Parser, Debug)]
57pub struct HugrArgs {
58    /// Input HUGR file, use '-' for stdin
59    #[clap(value_parser, default_value = "-")]
60    pub input: Input,
61    /// Verbosity.
62    #[command(flatten)]
63    pub verbose: Verbosity<InfoLevel>,
64    /// No standard extensions.
65    #[arg(
66        long,
67        help = "Don't use standard extensions when validating. Prelude is still used."
68    )]
69    pub no_std: bool,
70    /// Extensions paths.
71    #[arg(
72        short,
73        long,
74        help = "Paths to serialised extensions to validate against."
75    )]
76    pub extensions: Vec<PathBuf>,
77}
78
79/// A simple enum containing either a package or a single hugr.
80///
81/// This is required since `Package`s can only contain module-rooted hugrs.
82#[derive(Debug, Clone, PartialEq)]
83pub enum PackageOrHugr {
84    /// A package with module-rooted HUGRs and some required extensions.
85    Package(Package),
86    /// An arbitrary HUGR.
87    Hugr(Hugr),
88}
89
90impl PackageOrHugr {
91    /// Returns the list of hugrs in the package.
92    pub fn into_hugrs(self) -> Vec<Hugr> {
93        match self {
94            PackageOrHugr::Package(pkg) => pkg.modules,
95            PackageOrHugr::Hugr(hugr) => vec![hugr],
96        }
97    }
98
99    /// Validates the package or hugr.
100    pub fn validate(&self) -> Result<(), PackageValidationError> {
101        match self {
102            PackageOrHugr::Package(pkg) => pkg.validate(),
103            PackageOrHugr::Hugr(hugr) => Ok(hugr.validate()?),
104        }
105    }
106}
107
108impl AsRef<[Hugr]> for PackageOrHugr {
109    fn as_ref(&self) -> &[Hugr] {
110        match self {
111            PackageOrHugr::Package(pkg) => &pkg.modules,
112            PackageOrHugr::Hugr(hugr) => std::slice::from_ref(hugr),
113        }
114    }
115}
116
117impl HugrArgs {
118    /// Read either a package or a single hugr from the input.
119    pub fn get_package_or_hugr(
120        &mut self,
121        extensions: &ExtensionRegistry,
122    ) -> Result<PackageOrHugr, CliError> {
123        // We need to read the input twice; once to try to load it as a HUGR, and if that fails, as a package.
124        // If `input` is a file, we can reuse the reader by seeking back to the start.
125        // Else, we need to read the file into a buffer.
126        match self.input.can_seek() {
127            true => get_package_or_hugr_seek(&mut self.input, extensions),
128            false => {
129                let mut buffer = Vec::new();
130                self.input.read_to_end(&mut buffer)?;
131                get_package_or_hugr_seek(Cursor::new(buffer), extensions)
132            }
133        }
134    }
135}
136
137/// Load a package or hugr from a seekable input.
138fn get_package_or_hugr_seek<I: Seek + Read>(
139    mut input: I,
140    extensions: &ExtensionRegistry,
141) -> Result<PackageOrHugr, CliError> {
142    match Hugr::load_json(&mut input, extensions) {
143        Ok(hugr) => Ok(PackageOrHugr::Hugr(hugr)),
144        Err(_) => {
145            input.seek(SeekFrom::Start(0))?;
146            let pkg = Package::from_json_reader(input, extensions)?;
147            Ok(PackageOrHugr::Package(pkg))
148        }
149    }
150}