wac_cli/commands/
compose.rs1use crate::{fmt_err, PackageResolver};
2use anyhow::{bail, Context, Result};
3use clap::Args;
4use std::{
5 fs,
6 io::{IsTerminal, Write},
7 path::PathBuf,
8};
9use wac_graph::EncodeOptions;
10use wac_parser::Document;
11use wasmprinter::print_bytes;
12
13fn parse<T, U>(s: &str) -> Result<(T, U)>
14where
15 T: std::str::FromStr,
16 T::Err: Into<anyhow::Error>,
17 U: std::str::FromStr,
18 U::Err: Into<anyhow::Error>,
19{
20 let (k, v) = s.split_once('=').context("value does not contain `=`")?;
21
22 Ok((
23 k.trim().parse().map_err(Into::into)?,
24 v.trim().parse().map_err(Into::into)?,
25 ))
26}
27
28#[derive(Args)]
30#[clap(disable_version_flag = true)]
31pub struct ComposeCommand {
32 #[clap(long, value_name = "PATH", default_value = "deps")]
34 pub deps_dir: PathBuf,
35
36 #[clap(long = "dep", short, value_name = "PKG=PATH", value_parser = parse::<String, PathBuf>)]
38 pub deps: Vec<(String, PathBuf)>,
39
40 #[clap(long)]
42 pub no_validate: bool,
43
44 #[clap(long, short = 't')]
46 pub wat: bool,
47
48 #[clap(long, short)]
54 pub import_dependencies: bool,
55
56 #[clap(long, short = 'o')]
60 pub output: Option<PathBuf>,
61
62 #[cfg(feature = "registry")]
64 #[clap(long, value_name = "URL")]
65 pub registry: Option<String>,
66
67 #[clap(value_name = "PATH")]
69 pub path: PathBuf,
70}
71
72impl ComposeCommand {
73 pub async fn exec(self) -> Result<()> {
75 log::debug!("executing compose command");
76
77 let contents = fs::read_to_string(&self.path)
78 .with_context(|| format!("failed to read file `{path}`", path = self.path.display()))?;
79
80 let document = Document::parse(&contents).map_err(|e| fmt_err(e, &self.path, &contents))?;
81
82 let mut resolver = PackageResolver::new(
83 self.deps_dir,
84 self.deps.into_iter().collect(),
85 #[cfg(feature = "registry")]
86 self.registry.as_deref(),
87 )
88 .await?;
89
90 let packages = resolver
91 .resolve(&document)
92 .await
93 .map_err(|e| fmt_err(e, &self.path, &contents))?;
94
95 let resolution = document
96 .resolve(packages)
97 .map_err(|e| fmt_err(e, &self.path, &contents))?;
98
99 if !self.wat && self.output.is_none() && std::io::stdout().is_terminal() {
100 bail!("cannot print binary wasm output to a terminal; pass the `-t` flag to print the text format instead");
101 }
102
103 let mut bytes = resolution.encode(EncodeOptions {
104 define_components: !self.import_dependencies,
105 validate: !self.no_validate,
106 ..Default::default()
107 })?;
108 if self.wat {
109 bytes = print_bytes(&bytes)
110 .context("failed to convert binary wasm output to text")?
111 .into_bytes();
112 }
113
114 match self.output {
115 Some(path) => {
116 fs::write(&path, bytes).context(format!(
117 "failed to write output file `{path}`",
118 path = path.display()
119 ))?;
120 }
121 None => {
122 std::io::stdout()
123 .write_all(&bytes)
124 .context("failed to write to stdout")?;
125
126 if self.wat {
127 println!();
128 }
129 }
130 }
131
132 Ok(())
133 }
134}