use anyhow::{bail, Context, Error, Result};
use clap::Parser;
use std::path::PathBuf;
use std::str;
use wit_bindgen_core::{wit_parser, Files, WorldGenerator};
use wit_parser::Resolve;
fn version() -> &'static str {
option_env!("CARGO_VERSION_INFO").unwrap_or(env!("CARGO_PKG_VERSION"))
}
#[derive(Debug, Parser)]
#[command(version = version())]
#[allow(clippy::large_enum_variant)]
enum Opt {
Rust {
#[clap(flatten)]
opts: wit_bindgen_wrpc_rust::Opts,
#[clap(flatten)]
args: Common,
},
Go {
#[clap(flatten)]
opts: wit_bindgen_wrpc_go::Opts,
#[clap(flatten)]
args: Common,
},
}
#[derive(Debug, Parser)]
struct Common {
#[clap(long = "out-dir")]
out_dir: Option<PathBuf>,
#[clap(value_name = "WIT", index = 1)]
wit: PathBuf,
#[clap(short, long)]
world: Option<String>,
#[clap(long)]
check: bool,
#[clap(long)]
features: Vec<String>,
#[clap(long)]
all_features: bool,
}
fn main() -> Result<()> {
let mut files = Files::default();
let (generator, opt) = match Opt::parse() {
Opt::Rust { opts, args } => (opts.build(), args),
Opt::Go { opts, args } => (opts.build(), args),
};
gen_world(generator, &opt, &mut files).map_err(attach_with_context)?;
for (name, contents) in files.iter() {
let dst = match &opt.out_dir {
Some(path) => path.join(name),
None => name.into(),
};
eprintln!("Generating {dst:?}");
if opt.check {
let prev = std::fs::read(&dst).with_context(|| format!("failed to read {dst:?}"))?;
if prev != contents {
if let (Ok(utf8_prev), Ok(utf8_contents)) =
(str::from_utf8(&prev), str::from_utf8(contents))
{
if !utf8_prev
.chars()
.any(|c| c.is_control() && !matches!(c, '\n' | '\r' | '\t'))
&& utf8_prev.lines().eq(utf8_contents.lines())
{
bail!("{} differs only in line endings (CRLF vs. LF). If this is a text file, configure git to mark the file as `text eol=lf`.", dst.display());
}
}
bail!("not up to date: {}", dst.display());
}
continue;
}
if let Some(parent) = dst.parent() {
std::fs::create_dir_all(parent)
.with_context(|| format!("failed to create {parent:?}"))?;
}
std::fs::write(&dst, contents).with_context(|| format!("failed to write {dst:?}"))?;
}
Ok(())
}
fn attach_with_context(err: Error) -> Error {
if let Some(e) = err.downcast_ref::<wit_bindgen_wrpc_rust::MissingWith>() {
let option = e.0.clone();
return err.context(format!(
"missing either `--generate-all` or `--with {option}=(...|generate)`"
));
}
err
}
fn gen_world(
mut generator: Box<dyn WorldGenerator>,
opts: &Common,
files: &mut Files,
) -> Result<()> {
let mut resolve = Resolve {
all_features: opts.all_features,
..Default::default()
};
for features in &opts.features {
for feature in features
.split(',')
.flat_map(|s| s.split_whitespace())
.filter(|f| !f.is_empty())
{
resolve.features.insert(feature.to_string());
}
}
let (pkg, _files) = resolve.push_path(&opts.wit)?;
let world = resolve.select_world(pkg, opts.world.as_deref())?;
generator.generate(&resolve, world, files)?;
Ok(())
}
#[test]
fn verify_cli() {
use clap::CommandFactory;
Opt::command().debug_assert();
}