use anyhow::{Context, Error, Result, bail};
use clap::Parser;
use std::path::PathBuf;
use std::str;
use wit_bindgen_core::{Files, WorldGenerator, wit_parser};
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())]
enum Opt {
#[cfg(feature = "markdown")]
Markdown {
#[clap(flatten)]
opts: wit_bindgen_markdown::Opts,
#[clap(flatten)]
args: Common,
},
#[cfg(feature = "moonbit")]
Moonbit {
#[clap(flatten)]
opts: wit_bindgen_moonbit::Opts,
#[clap(flatten)]
args: Common,
},
#[cfg(feature = "rust")]
Rust {
#[clap(flatten)]
opts: wit_bindgen_rust::Opts,
#[clap(flatten)]
args: Common,
},
#[cfg(feature = "c")]
C {
#[clap(flatten)]
opts: wit_bindgen_c::Opts,
#[clap(flatten)]
args: Common,
},
#[cfg(feature = "cpp")]
Cpp {
#[clap(flatten)]
opts: wit_bindgen_cpp::Opts,
#[clap(flatten)]
args: Common,
},
#[cfg(feature = "go")]
Go {
#[clap(flatten)]
opts: wit_bindgen_go::Opts,
#[clap(flatten)]
args: Common,
},
#[cfg(feature = "csharp")]
#[command(alias = "c-sharp")]
Csharp {
#[clap(flatten)]
opts: wit_bindgen_csharp::Opts,
#[clap(flatten)]
args: Common,
},
Test {
#[clap(flatten)]
opts: wit_bindgen_test::Opts,
},
}
#[derive(Debug, Parser)]
struct Common {
#[clap(long = "out-dir")]
out_dir: Option<PathBuf>,
#[clap(value_name = "WIT", index = 1)]
wit: Vec<PathBuf>,
#[clap(short, long)]
world: Option<String>,
#[clap(long)]
check: bool,
#[clap(long)]
features: Vec<String>,
#[clap(long)]
all_features: bool,
}
fn main() -> Result<()> {
env_logger::init();
let mut files = Files::default();
let (generator, opt) = match Opt::parse() {
#[cfg(feature = "markdown")]
Opt::Markdown { opts, args } => (opts.build(), args),
#[cfg(feature = "moonbit")]
Opt::Moonbit { opts, args } => (opts.build(), args),
#[cfg(feature = "c")]
Opt::C { opts, args } => (opts.build(), args),
#[cfg(feature = "cpp")]
Opt::Cpp { opts, args } => (opts.build(args.out_dir.as_ref()), args),
#[cfg(feature = "rust")]
Opt::Rust { opts, args } => (Box::new(opts.build()) as Box<dyn WorldGenerator>, args),
#[cfg(feature = "go")]
Opt::Go { opts, args } => (opts.build(), args),
#[cfg(feature = "csharp")]
Opt::Csharp { opts, args } => (opts.build(), args),
Opt::Test { opts } => return opts.run(std::env::args_os().nth(0).unwrap().as_ref()),
};
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 {
#[cfg(feature = "rust")]
if let Some(e) = err.downcast_ref::<wit_bindgen_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::default();
resolve.all_features = opts.all_features;
for features in opts.features.iter() {
for feature in features
.split(',')
.flat_map(|s| s.split_whitespace())
.filter(|f| !f.is_empty())
{
resolve.features.insert(feature.to_string());
}
}
let mut main_packages = Vec::new();
for wit in &opts.wit {
let (pkg, _files) = resolve.push_path(wit)?;
main_packages.push(pkg);
}
let world = resolve.select_world(&main_packages, opts.world.as_deref())?;
generator.generate(&mut resolve, world, files)?;
Ok(())
}
#[test]
fn verify_cli() {
use clap::CommandFactory;
Opt::command().debug_assert()
}