1use std::{fmt::Display, io::Write, path::PathBuf, str::FromStr};
2
3use anyhow::{Context, Error};
4use clap::Parser;
5use wasmer_pack::{Metadata, Package};
6
7#[derive(Debug, Parser)]
8pub struct Show {
9 #[clap(short, long, default_value_t = Format::Text)]
11 format: Format,
12 input: PathBuf,
14}
15
16impl Show {
17 pub fn run(self) -> Result<(), Error> {
18 let pkg = crate::pirita::load_from_disk(&self.input).with_context(|| {
19 format!("Unable to load a package from \"{}\"", self.input.display())
20 })?;
21
22 let summary: Summary = summarize(&pkg);
23
24 let mut stdout = std::io::stdout();
25 match self.format {
26 Format::Json => {
27 summary.write_json(stdout.lock())?;
28 writeln!(stdout)?;
29 }
30 Format::Text => {
31 summary.dump(stdout.lock())?;
32 }
33 }
34
35 Ok(())
36 }
37}
38
39fn summarize(pkg: &Package) -> Summary {
40 let Metadata {
41 description,
42 package_name,
43 version,
44 ..
45 } = pkg.metadata();
46
47 let bindings = pkg
48 .libraries()
49 .iter()
50 .map(|lib| Library {
51 interface_name: lib.interface_name().to_string(),
52 wasi: lib.requires_wasi(),
53 })
54 .collect();
55
56 let commands = pkg
57 .commands()
58 .iter()
59 .map(|cmd| Command {
60 name: cmd.name.clone(),
61 })
62 .collect();
63
64 Summary {
65 description: description.clone(),
66 name: package_name.to_string(),
67 version: version.clone(),
68 bindings,
69 commands,
70 }
71}
72
73#[derive(Debug, serde::Serialize)]
74struct Summary {
75 name: String,
76 version: String,
77 description: Option<String>,
78 bindings: Vec<Library>,
79 commands: Vec<Command>,
80}
81
82impl Summary {
83 fn write_json(&self, writer: impl Write) -> Result<(), Error> {
84 serde_json::to_writer_pretty(writer, self)?;
85 Ok(())
86 }
87
88 fn dump(&self, mut writer: impl Write) -> Result<(), Error> {
89 let Summary {
90 name,
91 version,
92 description,
93 commands,
94 bindings,
95 } = self;
96
97 writeln!(writer, "{name} {version}")?;
98
99 if let Some(description) = description {
100 writeln!(writer, "{description}")?;
101 }
102
103 if !commands.is_empty() {
104 writeln!(writer, "Commands:")?;
105 for command in commands {
106 command.dump(&mut writer)?;
107 }
108 }
109
110 if !bindings.is_empty() {
111 writeln!(writer, "Bindings:")?;
112 for lib in bindings {
113 lib.dump(&mut writer)?;
114 }
115 }
116
117 Ok(())
118 }
119}
120
121#[derive(Debug, serde::Serialize)]
122struct Command {
123 name: String,
124}
125
126impl Command {
127 fn dump(&self, mut writer: impl Write) -> Result<(), Error> {
128 let Command { name } = self;
129
130 writeln!(writer, "- {name}")?;
131
132 Ok(())
133 }
134}
135
136#[derive(Debug, serde::Serialize)]
137struct Library {
138 interface_name: String,
139 wasi: bool,
140}
141
142impl Library {
143 fn dump(&self, mut writer: impl Write) -> Result<(), Error> {
144 let Library {
145 interface_name,
146 wasi,
147 } = self;
148
149 write!(writer, "- {interface_name}")?;
150
151 if *wasi {
152 write!(writer, " (wasi)")?;
153 }
154
155 writeln!(writer)?;
156
157 Ok(())
158 }
159}
160
161#[derive(Debug, Copy, Clone, PartialEq, Eq, clap::ValueEnum)]
162pub enum Format {
163 Json,
164 Text,
165}
166
167impl Display for Format {
168 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
169 match self {
170 Format::Json => f.write_str("json"),
171 Format::Text => f.write_str("text"),
172 }
173 }
174}
175
176impl FromStr for Format {
177 type Err = Error;
178
179 fn from_str(s: &str) -> Result<Self, Self::Err> {
180 match s {
181 "json" => Ok(Format::Json),
182 "text" => Ok(Format::Text),
183 other => anyhow::bail!("Expected \"json\" or \"text\", found \"{other}\""),
184 }
185 }
186}