1use anyhow::{bail, Context, Result};
4use std::fs::File;
5use std::io::IsTerminal;
6use std::io::{BufWriter, Read, Write};
7use std::path::{Path, PathBuf};
8use std::str::FromStr;
9use termcolor::{Ansi, ColorChoice, NoColor, StandardStream, WriteColor};
10
11#[cfg(any(feature = "addr2line", feature = "validate"))]
12pub mod addr2line;
13
14#[derive(clap::Parser)]
15pub struct GeneralOpts {
16 #[clap(long = "verbose", short = 'v', action = clap::ArgAction::Count)]
18 verbose: u8,
19
20 #[clap(long = "color", default_value = "auto")]
26 pub color: ColorChoice,
27}
28
29impl GeneralOpts {
30 pub fn init_logger(&self) {
32 let default = match self.verbose {
33 0 => "warn",
34 1 => "info",
35 2 => "debug",
36 _ => "trace",
37 };
38
39 env_logger::Builder::from_env(env_logger::Env::default().default_filter_or(default))
40 .format_target(false)
41 .init();
42 }
43}
44
45#[derive(clap::Parser)]
52pub struct InputOutput {
53 #[clap(flatten)]
54 input: InputArg,
55
56 #[clap(flatten)]
57 output: OutputArg,
58
59 #[clap(flatten)]
60 general: GeneralOpts,
61}
62
63#[derive(clap::Parser)]
64pub struct InputArg {
65 input: Option<PathBuf>,
71
72 #[clap(
82 long,
83 value_name = "lines|full",
84 conflicts_with = "generate_full_dwarf"
85 )]
86 generate_dwarf: Option<GenerateDwarf>,
87
88 #[clap(short, conflicts_with = "generate_dwarf")]
90 generate_full_dwarf: bool,
91}
92
93#[derive(Copy, Clone)]
94enum GenerateDwarf {
95 Lines,
96 Full,
97}
98
99impl FromStr for GenerateDwarf {
100 type Err = anyhow::Error;
101
102 fn from_str(s: &str) -> Result<GenerateDwarf> {
103 match s {
104 "lines" => Ok(GenerateDwarf::Lines),
105 "full" => Ok(GenerateDwarf::Full),
106 other => bail!("unknown `--generate-dwarf` setting: {other}"),
107 }
108 }
109}
110
111impl InputArg {
112 pub fn parse_wasm(&self) -> Result<Vec<u8>> {
113 let mut parser = wat::Parser::new();
114 match (self.generate_full_dwarf, self.generate_dwarf) {
115 (false, Some(GenerateDwarf::Lines)) => {
116 parser.generate_dwarf(wat::GenerateDwarf::Lines);
117 }
118 (true, _) | (false, Some(GenerateDwarf::Full)) => {
119 parser.generate_dwarf(wat::GenerateDwarf::Full);
120 }
121 (false, None) => {}
122 }
123 if let Some(path) = &self.input {
124 if path != Path::new("-") {
125 let bytes = parser.parse_file(path)?;
126 return Ok(bytes);
127 }
128 }
129 let mut stdin = Vec::new();
130 std::io::stdin()
131 .read_to_end(&mut stdin)
132 .context("failed to read <stdin>")?;
133 let bytes = parser.parse_bytes(Some("<stdin>".as_ref()), &stdin)?;
134 Ok(bytes.into_owned())
135 }
136}
137
138#[derive(clap::Parser)]
139pub struct OutputArg {
140 #[clap(short, long)]
144 output: Option<PathBuf>,
145}
146
147pub enum Output<'a> {
148 #[cfg(feature = "component")]
149 Wit {
150 wit: &'a wit_component::DecodedWasm,
151 printer: wit_component::WitPrinter,
152 },
153 Wasm(&'a [u8]),
154 Wat {
155 wasm: &'a [u8],
156 config: wasmprinter::Config,
157 },
158 Json(&'a str),
159}
160
161impl InputOutput {
162 pub fn parse_input_wasm(&self) -> Result<Vec<u8>> {
163 self.input.parse_wasm()
164 }
165
166 pub fn output_wasm(&self, wasm: &[u8], wat: bool) -> Result<()> {
167 if wat {
168 self.output(Output::Wat {
169 wasm,
170 config: Default::default(),
171 })
172 } else {
173 self.output(Output::Wasm(wasm))
174 }
175 }
176
177 pub fn output(&self, bytes: Output<'_>) -> Result<()> {
178 self.output.output(&self.general, bytes)
179 }
180
181 pub fn output_writer(&self) -> Result<Box<dyn WriteColor>> {
182 self.output.output_writer(self.general.color)
183 }
184
185 pub fn output_path(&self) -> Option<&Path> {
186 self.output.output.as_deref()
187 }
188
189 pub fn input_path(&self) -> Option<&Path> {
190 self.input.input.as_deref()
191 }
192
193 pub fn general_opts(&self) -> &GeneralOpts {
194 &self.general
195 }
196}
197
198impl OutputArg {
199 pub fn output_wasm(&self, general: &GeneralOpts, wasm: &[u8], wat: bool) -> Result<()> {
200 if wat {
201 self.output(
202 general,
203 Output::Wat {
204 wasm,
205 config: Default::default(),
206 },
207 )
208 } else {
209 self.output(general, Output::Wasm(wasm))
210 }
211 }
212
213 pub fn output(&self, general: &GeneralOpts, output: Output<'_>) -> Result<()> {
214 match output {
215 Output::Wat { wasm, config } => {
216 let mut writer = self.output_writer(general.color)?;
217 config.print(wasm, &mut wasmprinter::PrintTermcolor(&mut writer))
218 }
219 Output::Wasm(bytes) => {
220 match &self.output {
221 Some(path) => {
222 std::fs::write(path, bytes)
223 .context(format!("failed to write `{}`", path.display()))?;
224 }
225 None => {
226 let mut stdout = std::io::stdout();
227 if stdout.is_terminal() {
228 bail!("cannot print binary wasm output to a terminal, pass the `-t` flag to print the text format");
229 }
230 stdout
231 .write_all(bytes)
232 .context("failed to write to stdout")?;
233 }
234 }
235 Ok(())
236 }
237 Output::Json(s) => self.output_str(s),
238 #[cfg(feature = "component")]
239 Output::Wit { wit, mut printer } => {
240 let resolve = wit.resolve();
241 let ids = resolve
242 .packages
243 .iter()
244 .map(|(id, _)| id)
245 .filter(|id| *id != wit.package())
246 .collect::<Vec<_>>();
247 printer.print(resolve, wit.package(), &ids)?;
248 let output = printer.output.to_string();
249 self.output_str(&output)
250 }
251 }
252 }
253
254 fn output_str(&self, output: &str) -> Result<()> {
255 match &self.output {
256 Some(path) => {
257 std::fs::write(path, output)
258 .context(format!("failed to write `{}`", path.display()))?;
259 }
260 None => std::io::stdout()
261 .write_all(output.as_bytes())
262 .context("failed to write to stdout")?,
263 }
264 Ok(())
265 }
266
267 pub fn output_path(&self) -> Option<&Path> {
268 self.output.as_deref()
269 }
270
271 pub fn output_writer(&self, color: ColorChoice) -> Result<Box<dyn WriteColor>> {
272 match &self.output {
273 Some(output) => {
274 let writer = BufWriter::new(File::create(&output)?);
275 if color == ColorChoice::AlwaysAnsi {
276 Ok(Box::new(Ansi::new(writer)))
277 } else {
278 Ok(Box::new(NoColor::new(writer)))
279 }
280 }
281 None => {
282 let stdout = std::io::stdout();
283 if color == ColorChoice::Auto && !stdout.is_terminal() {
284 Ok(Box::new(StandardStream::stdout(ColorChoice::Never)))
285 } else {
286 Ok(Box::new(StandardStream::stdout(color)))
287 }
288 }
289 }
290 }
291}