1use crate::hugr_io::HugrInputArgs;
3use anyhow::Result;
4use clap::Parser;
5use clio::Output;
6use hugr::NodeIndex;
7use hugr::envelope::ReadError;
8use hugr::envelope::description::{ExtensionDesc, ModuleDesc, PackageDesc};
9use hugr::extension::Version;
10use hugr::package::Package;
11use std::io::Write;
12use tabled::Tabled;
13use tabled::derive::display;
14
15#[derive(Parser, Debug)]
17#[clap(version = "1.0", long_about = None)]
18#[clap(about = "Describe the contents of a HUGR envelope.")]
19#[group(id = "hugr")]
20#[non_exhaustive]
21pub struct DescribeArgs {
22 #[command(flatten)]
24 pub input_args: HugrInputArgs,
25 #[arg(long, default_value = "false", help_heading = "Filter")]
27 pub packaged_extensions: bool,
28
29 #[command(flatten)]
30 pub module_args: ModuleArgs,
32
33 #[arg(long, default_value = "false", help_heading = "JSON")]
34 pub json: bool,
36
37 #[arg(long, default_value = "false", help_heading = "JSON")]
38 pub json_schema: bool,
41
42 #[clap(short, long, value_parser, default_value = "-")]
44 pub output: Output,
45}
46
47#[derive(Debug, clap::Args)]
49pub struct ModuleArgs {
50 #[arg(long, default_value = "false", help_heading = "Filter")]
51 pub no_resolved_extensions: bool,
53
54 #[arg(long, default_value = "false", help_heading = "Filter")]
55 pub public_symbols: bool,
57
58 #[arg(long, default_value = "false", help_heading = "Filter")]
59 pub generator_claimed_extensions: bool,
61}
62impl ModuleArgs {
63 fn filter_module(&self, module: &mut ModuleDesc) {
64 if self.no_resolved_extensions {
65 module.used_extensions_resolved = None;
66 }
67 if !self.public_symbols {
68 module.public_symbols = None;
69 }
70 if !self.generator_claimed_extensions {
71 module.used_extensions_generator = None;
72 }
73 }
74}
75impl DescribeArgs {
76 pub fn run_describe(&mut self) -> Result<()> {
78 if self.json_schema {
79 let schema = schemars::schema_for!(PackageDescriptionJson);
80 let schema_json = serde_json::to_string_pretty(&schema)?;
81 writeln!(self.output, "{schema_json}")?;
82 return Ok(());
83 }
84 let (mut desc, res) = match self.input_args.get_described_package() {
85 Ok((desc, pkg)) => (desc, Ok(pkg)),
86 Err(crate::CliError::ReadEnvelope(ReadError::Payload {
87 source,
88 partial_description,
89 })) => (partial_description, Err(source)), Err(e) => return Err(e.into()),
91 };
92
93 for module in desc.modules.iter_mut().flatten() {
95 self.module_args.filter_module(module);
96 }
97
98 let res = res.map_err(anyhow::Error::from);
99 if self.json {
100 if !self.packaged_extensions {
101 desc.packaged_extensions.clear();
102 }
103 self.output_json(desc, &res)?;
104 } else {
105 self.print_description(desc)?;
106 }
107
108 res.map(|_| ())
110 }
111
112 fn print_description(&mut self, desc: PackageDesc) -> Result<()> {
113 let header = desc.header();
114 let n_modules = desc.n_modules();
115 let n_extensions = desc.n_packaged_extensions();
116 let module_str = if n_modules == 1 { "module" } else { "modules" };
117 let extension_str = if n_extensions == 1 {
118 "extension"
119 } else {
120 "extensions"
121 };
122 writeln!(
123 self.output,
124 "{header}\nPackage contains {n_modules} {module_str} and {n_extensions} {extension_str}",
125 )?;
126 let summaries: Vec<ModuleSummary> = desc
127 .modules
128 .iter()
129 .map(|m| m.as_ref().map(Into::into).unwrap_or_default())
130 .collect();
131 let summary_table = tabled::Table::builder(summaries).index().build();
132 writeln!(self.output, "{summary_table}")?;
133
134 for (i, module) in desc.modules.into_iter().enumerate() {
135 writeln!(self.output, "\nModule {i}:")?;
136 if let Some(module) = module {
137 self.display_module(module)?;
138 }
139 }
140 if self.packaged_extensions {
141 writeln!(self.output, "Packaged extensions:")?;
142 let ext_rows: Vec<ExtensionRow> = desc
143 .packaged_extensions
144 .into_iter()
145 .flatten()
146 .map(Into::into)
147 .collect();
148 let ext_table = tabled::Table::new(ext_rows);
149 writeln!(self.output, "{ext_table}")?;
150 }
151 Ok(())
152 }
153
154 fn output_json(&mut self, package_desc: PackageDesc, res: &Result<Package>) -> Result<()> {
155 let err_str = res.as_ref().err().map(|e| format!("{e:?}"));
156 let json_desc = PackageDescriptionJson {
157 package_desc,
158 error: err_str,
159 };
160 serde_json::to_writer_pretty(&mut self.output, &json_desc)?;
161 Ok(())
162 }
163
164 fn display_module(&mut self, desc: ModuleDesc) -> Result<()> {
165 if let Some(exts) = desc.used_extensions_resolved {
166 let ext_rows: Vec<ExtensionRow> = exts.into_iter().map(Into::into).collect();
167 let ext_table = tabled::Table::new(ext_rows);
168 writeln!(self.output, "Resolved extensions:\n{ext_table}")?;
169 }
170
171 if let Some(syms) = desc.public_symbols {
172 let sym_table = tabled::Table::new(syms.into_iter().map(|s| SymbolRow { symbol: s }));
173 writeln!(self.output, "Public symbols:\n{sym_table}")?;
174 }
175
176 if let Some(exts) = desc.used_extensions_generator {
177 let ext_rows: Vec<ExtensionRow> = exts.into_iter().map(Into::into).collect();
178 let ext_table = tabled::Table::new(ext_rows);
179 writeln!(self.output, "Generator claimed extensions:\n{ext_table}")?;
180 }
181
182 Ok(())
183 }
184}
185
186#[derive(serde::Serialize, schemars::JsonSchema)]
187struct PackageDescriptionJson {
188 #[serde(flatten)]
189 package_desc: PackageDesc,
190 #[serde(skip_serializing_if = "Option::is_none")]
191 error: Option<String>,
192}
193
194#[derive(Tabled)]
195struct ExtensionRow {
196 name: String,
197 version: Version,
198}
199
200#[derive(Tabled)]
201struct SymbolRow {
202 #[tabled(rename = "Symbol")]
203 symbol: String,
204}
205
206impl From<ExtensionDesc> for ExtensionRow {
207 fn from(desc: ExtensionDesc) -> Self {
208 Self {
209 name: desc.name,
210 version: desc.version,
211 }
212 }
213}
214
215#[derive(Tabled, Default)]
216struct ModuleSummary {
217 #[tabled(display("display::option", "n/a"))]
218 num_nodes: Option<usize>,
219 #[tabled(display("display::option", "n/a"))]
220 entrypoint_node: Option<usize>,
221 #[tabled(display("display::option", "n/a"))]
222 entrypoint_op: Option<String>,
223 #[tabled(display("display::option", "n/a"))]
224 generator: Option<String>,
225}
226
227impl From<&ModuleDesc> for ModuleSummary {
228 fn from(desc: &ModuleDesc) -> Self {
229 let (entrypoint_node, entrypoint_op) = if let Some(ep) = &desc.entrypoint {
230 (
231 Some(ep.node.index()),
232 Some(hugr::envelope::description::op_string(&ep.optype)),
233 )
234 } else {
235 (None, None)
236 };
237 Self {
238 num_nodes: desc.num_nodes,
239 entrypoint_node,
240 entrypoint_op,
241 generator: desc.generator.clone(),
242 }
243 }
244}