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::{Read, 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_with_io<R: Read, W: Write>(
83 &mut self,
84 input_override: Option<R>,
85 mut output_override: Option<W>,
86 ) -> Result<()> {
87 if self.json_schema {
88 let schema = schemars::schema_for!(PackageDescriptionJson);
89 let schema_json = serde_json::to_string_pretty(&schema)?;
90 if let Some(ref mut writer) = output_override {
91 writeln!(writer, "{schema_json}")?;
92 } else {
93 writeln!(self.output, "{schema_json}")?;
94 }
95 return Ok(());
96 }
97
98 let (mut desc, res) = match self
99 .input_args
100 .get_described_package_with_reader(input_override)
101 {
102 Ok((desc, pkg)) => (desc, Ok(pkg)),
103 Err(crate::CliError::ReadEnvelope(ReadError::Payload {
104 source,
105 partial_description,
106 })) => (partial_description, Err(source)),
107 Err(e) => return Err(e.into()),
108 };
109
110 for module in desc.modules.iter_mut().flatten() {
112 self.module_args.filter_module(module);
113 }
114
115 let res = res.map_err(anyhow::Error::from);
116
117 let writer: &mut dyn Write = if let Some(ref mut w) = output_override {
118 w
119 } else {
120 &mut self.output
121 };
122
123 if self.json {
124 if !self.packaged_extensions {
125 desc.packaged_extensions.clear();
126 }
127 output_json(desc, &res, writer)?;
128 } else {
129 print_description(desc, self.packaged_extensions, writer)?;
130 }
131
132 res.map(|_| ())
134 }
135
136 pub fn run_describe(&mut self) -> Result<()> {
138 self.run_describe_with_io(None::<&[u8]>, None::<Vec<u8>>)
139 }
140}
141
142fn print_description<W: Write + ?Sized>(
144 desc: PackageDesc,
145 show_packaged_extensions: bool,
146 writer: &mut W,
147) -> Result<()> {
148 let header = desc.header();
149 let n_modules = desc.n_modules();
150 let n_extensions = desc.n_packaged_extensions();
151 let module_str = if n_modules == 1 { "module" } else { "modules" };
152 let extension_str = if n_extensions == 1 {
153 "extension"
154 } else {
155 "extensions"
156 };
157
158 writeln!(
159 writer,
160 "{header}\nPackage contains {n_modules} {module_str} and {n_extensions} {extension_str}",
161 )?;
162
163 let summaries: Vec<ModuleSummary> = desc
164 .modules
165 .iter()
166 .map(|m| m.as_ref().map(Into::into).unwrap_or_default())
167 .collect();
168 let summary_table = tabled::Table::builder(summaries).index().build();
169 writeln!(writer, "{summary_table}")?;
170
171 for (i, module) in desc.modules.into_iter().enumerate() {
172 writeln!(writer, "\nModule {i}:")?;
173 if let Some(module) = module {
174 display_module(module, writer)?;
175 }
176 }
177 if show_packaged_extensions {
178 writeln!(writer, "Packaged extensions:")?;
179 let ext_rows: Vec<ExtensionRow> = desc
180 .packaged_extensions
181 .into_iter()
182 .flatten()
183 .map(Into::into)
184 .collect();
185 let ext_table = tabled::Table::new(ext_rows);
186 writeln!(writer, "{ext_table}")?;
187 }
188 Ok(())
189}
190
191fn output_json<W: Write + ?Sized>(
193 package_desc: PackageDesc,
194 res: &Result<Package>,
195 writer: &mut W,
196) -> Result<()> {
197 let err_str = res.as_ref().err().map(|e| format!("{e:?}"));
198 let json_desc = PackageDescriptionJson {
199 package_desc,
200 error: err_str,
201 };
202 serde_json::to_writer_pretty(writer, &json_desc)?;
203 Ok(())
204}
205
206fn display_module<W: Write + ?Sized>(desc: ModuleDesc, writer: &mut W) -> Result<()> {
208 if let Some(exts) = desc.used_extensions_resolved {
209 let ext_rows: Vec<ExtensionRow> = exts.into_iter().map(Into::into).collect();
210 let ext_table = tabled::Table::new(ext_rows);
211 writeln!(writer, "Resolved extensions:\n{ext_table}")?;
212 }
213
214 if let Some(syms) = desc.public_symbols {
215 let sym_table = tabled::Table::new(syms.into_iter().map(|s| SymbolRow { symbol: s }));
216 writeln!(writer, "Public symbols:\n{sym_table}")?;
217 }
218
219 if let Some(exts) = desc.used_extensions_generator {
220 let ext_rows: Vec<ExtensionRow> = exts.into_iter().map(Into::into).collect();
221 let ext_table = tabled::Table::new(ext_rows);
222 writeln!(writer, "Generator claimed extensions:\n{ext_table}")?;
223 }
224
225 Ok(())
226}
227
228#[derive(serde::Serialize, schemars::JsonSchema)]
229struct PackageDescriptionJson {
230 #[serde(flatten)]
231 package_desc: PackageDesc,
232 #[serde(skip_serializing_if = "Option::is_none")]
233 error: Option<String>,
234}
235
236#[derive(Tabled)]
237struct ExtensionRow {
238 name: String,
239 version: Version,
240}
241
242#[derive(Tabled)]
243struct SymbolRow {
244 #[tabled(rename = "Symbol")]
245 symbol: String,
246}
247
248impl From<ExtensionDesc> for ExtensionRow {
249 fn from(desc: ExtensionDesc) -> Self {
250 Self {
251 name: desc.name,
252 version: desc.version,
253 }
254 }
255}
256
257#[derive(Tabled, Default)]
258struct ModuleSummary {
259 #[tabled(display("display::option", "n/a"))]
260 num_nodes: Option<usize>,
261 #[tabled(display("display::option", "n/a"))]
262 entrypoint_node: Option<usize>,
263 #[tabled(display("display::option", "n/a"))]
264 entrypoint_op: Option<String>,
265 #[tabled(display("display::option", "n/a"))]
266 generator: Option<String>,
267}
268
269impl From<&ModuleDesc> for ModuleSummary {
270 fn from(desc: &ModuleDesc) -> Self {
271 let (entrypoint_node, entrypoint_op) = if let Some(ep) = &desc.entrypoint {
272 (
273 Some(ep.node.index()),
274 Some(hugr::envelope::description::op_string(&ep.optype)),
275 )
276 } else {
277 (None, None)
278 };
279 Self {
280 num_nodes: desc.num_nodes,
281 entrypoint_node,
282 entrypoint_op,
283 generator: desc.generator.clone(),
284 }
285 }
286}