Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(hugr-cli)!: move mermaid to own sub-command #1390

Merged
merged 7 commits into from
Aug 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ typetag = "0.2.7"
urlencoding = "2.1.2"
webbrowser = "1.0.0"
clap = { version = "4.5.4" }
clap-stdin = "0.5.0"
clio = "0.3.5"
clap-verbosity-flag = "2.2.0"
assert_cmd = "2.0.14"
assert_fs = "1.1.1"
Expand Down
2 changes: 1 addition & 1 deletion hugr-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ categories = ["compilers"]

[dependencies]
clap = { workspace = true, features = ["derive"] }
clap-stdin.workspace = true
clap-verbosity-flag.workspace = true
hugr-core = { path = "../hugr-core", version = "0.7.0" }
serde_json.workspace = true
serde.workspace = true
thiserror.workspace = true
clio = { workspace = true, features = ["clap-parse"] }

[lints]
workspace = true
Expand Down
79 changes: 74 additions & 5 deletions hugr-cli/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
//! Standard command line tools, used by the hugr binary.

use std::ffi::OsString;
use clap::Parser;
use clap_verbosity_flag::{InfoLevel, Verbosity};
use clio::Input;
use hugr_core::{Extension, Hugr};
use std::{ffi::OsString, path::PathBuf};
use thiserror::Error;
/// We reexport some clap types that are used in the public API.
pub use {clap::Parser, clap_verbosity_flag::Level};

pub mod extensions;
pub mod mermaid;
pub mod validate;

/// CLI arguments.
Expand All @@ -16,9 +19,11 @@ pub mod validate;
#[non_exhaustive]
pub enum CliArgs {
/// Validate and visualize a HUGR file.
Validate(validate::CliArgs),
Validate(validate::ValArgs),
/// Write standard extensions out in serialized form.
GenExtensions(extensions::ExtArgs),
/// Write HUGR as mermaid diagrams.
Mermaid(mermaid::MermaidArgs),
/// External commands
#[command(external_subcommand)]
External(Vec<OsString>),
Expand All @@ -29,6 +34,70 @@ pub enum CliArgs {
#[error(transparent)]
#[non_exhaustive]
pub enum CliError {
/// Error reading input.
#[error("Error reading from path: {0}")]
InputFile(#[from] std::io::Error),
/// Error parsing input.
#[error("Error parsing input: {0}")]
Parse(#[from] serde_json::Error),
/// Errors produced by the `validate` subcommand.
Validate(#[from] validate::CliError),
Validate(#[from] validate::ValError),
}

/// Validate and visualise a HUGR file.
#[derive(Parser, Debug)]
pub struct HugrArgs {
/// Input HUGR file, use '-' for stdin
#[clap(value_parser, default_value = "-")]
pub input: Input,
/// Verbosity.
#[command(flatten)]
pub verbose: Verbosity<InfoLevel>,
/// No standard extensions.
#[arg(
long,
help = "Don't use standard extensions when validating. Prelude is still used."
)]
pub no_std: bool,
/// Extensions paths.
#[arg(
short,
long,
help = "Paths to serialised extensions to validate against."
)]
pub extensions: Vec<PathBuf>,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
/// Package of module HUGRs and extensions.
/// The HUGRs are validated against the extensions.
pub struct Package {
/// Module HUGRs included in the package.
pub modules: Vec<Hugr>,
/// Extensions to validate against.
pub extensions: Vec<Extension>,
}

impl Package {
/// Create a new package.
pub fn new(modules: Vec<Hugr>, extensions: Vec<Extension>) -> Self {
Self {
modules,
extensions,
}
}
}

impl HugrArgs {
/// Read either a package or a single hugr from the input.
pub fn get_package(&mut self) -> Result<Package, CliError> {
let val: serde_json::Value = serde_json::from_reader(&mut self.input)?;
// read either a package or a single hugr
if let Ok(p) = serde_json::from_value::<Package>(val.clone()) {
Ok(p)
} else {
let hugr: Hugr = serde_json::from_value(val)?;
Ok(Package::new(vec![hugr], vec![]))
}
}
}
3 changes: 2 additions & 1 deletion hugr-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ fn main() {
match CliArgs::parse() {
CliArgs::Validate(args) => run_validate(args),
CliArgs::GenExtensions(args) => args.run_dump(),
CliArgs::Mermaid(mut args) => args.run_print().unwrap(),
CliArgs::External(_) => {
// TODO: Implement support for external commands.
// Running `hugr COMMAND` would look for `hugr-COMMAND` in the path
Expand All @@ -25,7 +26,7 @@ fn main() {
}

/// Run the `validate` subcommand.
fn run_validate(args: validate::CliArgs) {
fn run_validate(mut args: validate::ValArgs) {
let result = args.run();

if let Err(e) = result {
Expand Down
43 changes: 43 additions & 0 deletions hugr-cli/src/mermaid.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//! Render mermaid diagrams.
use std::io::Write;

use clap::Parser;
use clio::Output;
use hugr_core::HugrView;

/// Dump the standard extensions.
#[derive(Parser, Debug)]
#[clap(version = "1.0", long_about = None)]
#[clap(about = "Render mermaid diagrams..")]
#[group(id = "hugr")]
#[non_exhaustive]
pub struct MermaidArgs {
/// Common arguments
#[command(flatten)]
pub hugr_args: crate::HugrArgs,
/// Validate package.
#[arg(
long,
help = "Validate before rendering, includes extension inference."
)]
pub validate: bool,
/// Output file '-' for stdout
#[clap(long, short, value_parser, default_value = "-")]
output: Output,
}

impl MermaidArgs {
/// Write the mermaid diagram to the output.
pub fn run_print(&mut self) -> Result<(), crate::CliError> {
let hugrs = if self.validate {
self.hugr_args.validate()?
} else {
self.hugr_args.get_package()?.modules
};

for hugr in hugrs {
write!(self.output, "{}", hugr.mermaid_string())?;
}
Ok(())
}
}
115 changes: 31 additions & 84 deletions hugr-cli/src/validate.rs
Original file line number Diff line number Diff line change
@@ -1,58 +1,28 @@
//! The `validate` subcommand.
use std::path::PathBuf;

use clap::Parser;
use clap_stdin::FileOrStdin;
use clap_verbosity_flag::{InfoLevel, Level, Verbosity};
use hugr_core::{extension::ExtensionRegistry, Extension, Hugr, HugrView as _};
use clap_verbosity_flag::Level;
use hugr_core::{extension::ExtensionRegistry, Extension, Hugr};
use thiserror::Error;

use crate::{CliError, HugrArgs, Package};

/// Validate and visualise a HUGR file.
#[derive(Parser, Debug)]
#[clap(version = "1.0", long_about = None)]
#[clap(about = "Validate a HUGR.")]
#[group(id = "hugr")]
#[non_exhaustive]
pub struct CliArgs {
/// The input hugr to parse.
pub input: FileOrStdin,
/// Visualise with mermaid.
#[arg(short, long, value_name = "MERMAID", help = "Visualise with mermaid.")]
pub mermaid: bool,
/// Skip validation.
#[arg(short, long, help = "Skip validation.")]
pub no_validate: bool,
/// Verbosity.
pub struct ValArgs {
#[command(flatten)]
pub verbose: Verbosity<InfoLevel>,
/// No standard extensions.
#[arg(
long,
help = "Don't use standard extensions when validating. Prelude is still used."
)]
pub no_std: bool,
/// Extensions paths.
#[arg(
short,
long,
help = "Paths to serialised extensions to validate against."
)]
pub extensions: Vec<PathBuf>,
/// common arguments
pub hugr_args: HugrArgs,
}

/// Error type for the CLI.
#[derive(Error, Debug)]
#[non_exhaustive]
pub enum CliError {
/// Error reading input.
#[error("Error reading input: {0}")]
Input(#[from] clap_stdin::StdinError),
/// Error reading input.
#[error("Error reading from path: {0}")]
InputFile(#[from] std::io::Error),
/// Error parsing input.
#[error("Error parsing input: {0}")]
Parse(#[from] serde_json::Error),
pub enum ValError {
/// Error validating HUGR.
#[error("Error validating HUGR: {0}")]
Validate(#[from] hugr_core::hugr::ValidationError),
Expand All @@ -61,45 +31,28 @@ pub enum CliError {
ExtReg(#[from] hugr_core::extension::ExtensionRegistryError),
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
/// Package of module HUGRs and extensions.
/// The HUGRs are validated against the extensions.
pub struct Package {
/// Module HUGRs included in the package.
pub modules: Vec<Hugr>,
/// Extensions to validate against.
pub extensions: Vec<Extension>,
}

impl Package {
/// Create a new package.
pub fn new(modules: Vec<Hugr>, extensions: Vec<Extension>) -> Self {
Self {
modules,
extensions,
}
}
}

/// String to print when validation is successful.
pub const VALID_PRINT: &str = "HUGR valid!";

impl CliArgs {
impl ValArgs {
/// Run the HUGR cli and validate against an extension registry.
pub fn run(&self) -> Result<Vec<Hugr>, CliError> {
let rdr = self.input.clone().into_reader()?;
let val: serde_json::Value = serde_json::from_reader(rdr)?;
// read either a package or a single hugr
let (mut modules, packed_exts) = if let Ok(Package {
modules,
extensions,
}) = serde_json::from_value::<Package>(val.clone())
{
(modules, extensions)
} else {
let hugr: Hugr = serde_json::from_value(val)?;
(vec![hugr], vec![])
};
pub fn run(&mut self) -> Result<Vec<Hugr>, CliError> {
self.hugr_args.validate()
}

/// Test whether a `level` message should be output.
pub fn verbosity(&self, level: Level) -> bool {
self.hugr_args.verbosity(level)
}
}

impl HugrArgs {
/// Load the package and validate against an extension registry.
pub fn validate(&mut self) -> Result<Vec<Hugr>, CliError> {
let Package {
mut modules,
extensions: packed_exts,
} = self.get_package()?;

let mut reg: ExtensionRegistry = if self.no_std {
hugr_core::extension::PRELUDE_REGISTRY.to_owned()
Expand All @@ -109,26 +62,20 @@ impl CliArgs {

// register packed extensions
for ext in packed_exts {
reg.register_updated(ext)?;
reg.register_updated(ext).map_err(ValError::ExtReg)?;
}

// register external extensions
for ext in &self.extensions {
let f = std::fs::File::open(ext)?;
let ext: Extension = serde_json::from_reader(f)?;
reg.register_updated(ext)?;
reg.register_updated(ext).map_err(ValError::ExtReg)?;
}

for hugr in modules.iter_mut() {
if self.mermaid {
println!("{}", hugr.mermaid_string());
}

if !self.no_validate {
hugr.update_validate(&reg)?;
if self.verbosity(Level::Info) {
eprintln!("{}", VALID_PRINT);
}
hugr.update_validate(&reg).map_err(ValError::Validate)?;
if self.verbosity(Level::Info) {
eprintln!("{}", VALID_PRINT);
}
}
Ok(modules)
Expand Down
Loading
Loading