use anyhow::anyhow;
use clap::{crate_authors, App, AppSettings, Arg};
use is_executable::IsExecutable;
use std::borrow::Cow;
use std::collections::HashMap;
use std::env;
use std::ffi::OsStr;
use std::path::PathBuf;
use std::process;
use std::process::Command;
use std::str;
struct SubCommand {
path: Option<PathBuf>,
name: Cow<'static, str>,
}
impl SubCommand {
pub fn find_all() -> anyhow::Result<Vec<Self>> {
let c2rust = env::current_exe()?;
let c2rust_name = c2rust
.file_name()
.ok_or_else(|| anyhow!("no file name for c2rust: {}", c2rust.display()))?
.to_str()
.ok_or_else(|| anyhow!("c2rust file name is not UTF-8: {}", c2rust.display()))?;
let dir = c2rust
.parent()
.ok_or_else(|| anyhow!("no directory: {}", c2rust.display()))?;
let mut sub_commands = Vec::new();
for entry in dir.read_dir()? {
let entry = entry?;
let file_type = entry.file_type()?;
let path = entry.path();
let name = path
.file_name()
.and_then(|name| name.to_str())
.and_then(|name| name.strip_prefix(c2rust_name))
.and_then(|name| name.strip_prefix('-'))
.map(|name| name.to_owned())
.map(Cow::from)
.filter(|_| file_type.is_file() || file_type.is_symlink())
.filter(|_| path.is_executable());
if let Some(name) = name {
sub_commands.push(Self {
path: Some(path),
name,
});
}
}
Ok(sub_commands)
}
pub fn known() -> impl Iterator<Item = Self> {
["transpile", "refactor", "instrument", "pdg", "analyze"]
.into_iter()
.map(|name| Self {
path: None,
name: name.into(),
})
}
pub fn all() -> anyhow::Result<impl Iterator<Item = Self>> {
Ok(Self::known().chain(Self::find_all()?))
}
pub fn invoke<I, S>(&self, args: I) -> anyhow::Result<()>
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
let path = self.path.as_ref().ok_or_else(|| {
anyhow!(
"known subcommand not found (probably not built): {}",
self.name
)
})?;
let status = Command::new(path).args(args).status()?;
process::exit(status.code().unwrap_or(1));
}
}
fn main() -> anyhow::Result<()> {
let sub_commands = SubCommand::all()?.collect::<Vec<_>>();
let sub_commands = sub_commands
.iter()
.map(|cmd| (cmd.name.as_ref(), cmd))
.collect::<HashMap<_, _>>();
let mut args = env::args_os();
let sub_command = args.nth(1);
let sub_command = sub_command
.as_ref()
.and_then(|arg| arg.to_str())
.and_then(|name| sub_commands.get(name));
if let Some(sub_command) = sub_command {
return sub_command.invoke(args);
}
let matches = App::new("C2Rust")
.version(env!("CARGO_PKG_VERSION"))
.author(crate_authors!(", "))
.settings(&[AppSettings::SubcommandRequiredElseHelp])
.subcommands(sub_commands.keys().map(|name| {
clap::SubCommand::with_name(name).arg(
Arg::with_name("args")
.multiple(true)
.allow_hyphen_values(true),
)
}))
.get_matches();
let sub_command_name = matches
.subcommand_name()
.ok_or_else(|| anyhow!("no subcommand"))?;
sub_commands[sub_command_name].invoke(args)
}