use crate::{fmt_err, PackageResolver};
use anyhow::{bail, Context, Result};
use clap::Args;
use std::{
fs,
io::{IsTerminal, Write},
path::PathBuf,
};
use wac_graph::EncodeOptions;
use wac_parser::Document;
use wasmprinter::print_bytes;
fn parse<T, U>(s: &str) -> Result<(T, U)>
where
T: std::str::FromStr,
T::Err: Into<anyhow::Error>,
U: std::str::FromStr,
U::Err: Into<anyhow::Error>,
{
let (k, v) = s.split_once('=').context("value does not contain `=`")?;
Ok((
k.trim().parse().map_err(Into::into)?,
v.trim().parse().map_err(Into::into)?,
))
}
#[derive(Args)]
#[clap(disable_version_flag = true)]
pub struct ComposeCommand {
#[clap(long, value_name = "PATH", default_value = "deps")]
pub deps_dir: PathBuf,
#[clap(long = "dep", short, value_name = "PKG=PATH", value_parser = parse::<String, PathBuf>)]
pub deps: Vec<(String, PathBuf)>,
#[clap(long)]
pub no_validate: bool,
#[clap(long, short = 't')]
pub wat: bool,
#[clap(long, short)]
pub import_dependencies: bool,
#[clap(long, short = 'o')]
pub output: Option<PathBuf>,
#[cfg(feature = "registry")]
#[clap(long, value_name = "URL")]
pub registry: Option<String>,
#[clap(value_name = "PATH")]
pub path: PathBuf,
}
impl ComposeCommand {
pub async fn exec(self) -> Result<()> {
log::debug!("executing compose command");
let contents = fs::read_to_string(&self.path)
.with_context(|| format!("failed to read file `{path}`", path = self.path.display()))?;
let document = Document::parse(&contents).map_err(|e| fmt_err(e, &self.path, &contents))?;
let mut resolver = PackageResolver::new(
self.deps_dir,
self.deps.into_iter().collect(),
#[cfg(feature = "registry")]
self.registry.as_deref(),
)
.await?;
let packages = resolver
.resolve(&document)
.await
.map_err(|e| fmt_err(e, &self.path, &contents))?;
let resolution = document
.resolve(packages)
.map_err(|e| fmt_err(e, &self.path, &contents))?;
if !self.wat && self.output.is_none() && std::io::stdout().is_terminal() {
bail!("cannot print binary wasm output to a terminal; pass the `-t` flag to print the text format instead");
}
let mut bytes = resolution.encode(EncodeOptions {
define_components: !self.import_dependencies,
validate: !self.no_validate,
..Default::default()
})?;
if self.wat {
bytes = print_bytes(&bytes)
.context("failed to convert binary wasm output to text")?
.into_bytes();
}
match self.output {
Some(path) => {
fs::write(&path, bytes).context(format!(
"failed to write output file `{path}`",
path = path.display()
))?;
}
None => {
std::io::stdout()
.write_all(&bytes)
.context("failed to write to stdout")?;
if self.wat {
println!();
}
}
}
Ok(())
}
}