mod fuzz;
mod inspect;
mod record;
mod run;
use std::fmt;
use std::path::Path;
use std::process::ExitCode;
use clap::{Parser, Subcommand};
use watoots::Manifest;
#[derive(Parser)]
#[command(name = "watoots", version, about, long_about = None)]
struct Cli {
#[command(subcommand)]
command: Command,
}
#[derive(Subcommand)]
enum Command {
Inspect(inspect::InspectArgs),
Run(run::RunArgs),
Profile(run::ProfileArgs),
Reload(run::ReloadArgs),
Record(record::RecordArgs),
Replay(record::ReplayArgs),
Fuzz(fuzz::FuzzArgs),
Diff(inspect::DiffArgs),
#[command(subcommand)]
Trace(record::TraceCommand),
#[command(subcommand)]
Wit(inspect::WitCommand),
}
fn main() -> ExitCode {
match dispatch(Cli::parse()) {
Ok(code) => code,
Err(failure) => {
eprintln!("watoots: {failure}");
ExitCode::FAILURE
}
}
}
fn dispatch(cli: Cli) -> Outcome {
match cli.command {
Command::Inspect(args) => inspect::inspect(&args),
Command::Run(args) => run::run(&args),
Command::Profile(args) => run::profile(&args),
Command::Reload(args) => run::reload(&args),
Command::Record(args) => record::record(&args),
Command::Replay(args) => record::replay(&args),
Command::Fuzz(args) => fuzz::fuzz(&args),
Command::Diff(args) => inspect::diff(&args),
Command::Wit(inspect::WitCommand::SemverCheck(args)) => inspect::semver_check(&args),
Command::Trace(command) => record::trace(command),
}
}
pub(crate) struct Failure(String);
impl fmt::Display for Failure {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
impl From<watoots::Error> for Failure {
fn from(err: watoots::Error) -> Self {
Self(err.message().to_string())
}
}
impl From<watoots_trace::Error> for Failure {
fn from(err: watoots_trace::Error) -> Self {
Self(err.message().to_string())
}
}
impl From<String> for Failure {
fn from(message: String) -> Self {
Self(message)
}
}
impl From<&str> for Failure {
fn from(message: &str) -> Self {
Self(message.to_string())
}
}
pub(crate) type Outcome = Result<ExitCode, Failure>;
pub(crate) fn verdict(ok: bool) -> Outcome {
Ok(if ok {
ExitCode::SUCCESS
} else {
ExitCode::FAILURE
})
}
pub(crate) fn read(path: &Path) -> Result<Vec<u8>, Failure> {
std::fs::read(path).map_err(|err| format!("cannot read {}: {err}", path.display()).into())
}
pub(crate) fn read_text(path: &Path) -> Result<String, Failure> {
std::fs::read_to_string(path)
.map_err(|err| format!("cannot read {}: {err}", path.display()).into())
}
pub(crate) fn write(path: &Path, bytes: impl AsRef<[u8]>) -> Result<(), Failure> {
std::fs::write(path, bytes)
.map_err(|err| format!("cannot write {}: {err}", path.display()).into())
}
pub(crate) fn read_manifest(path: Option<&Path>) -> Result<(Manifest, String), Failure> {
match path {
None => Ok((Manifest::default(), String::new())),
Some(path) => {
let text = read_text(path)?;
let manifest = Manifest::parse(&text)
.map_err(|err| format!("{}: {}", path.display(), err.message()))?;
Ok((manifest, text))
}
}
}
pub(crate) fn plugin_name(component: &Path) -> String {
component.file_stem().map_or_else(
|| "plugin".to_string(),
|stem| stem.to_string_lossy().into(),
)
}