mod cli;
mod commands;
mod signal;
use std::io::Write;
use std::process::ExitCode;
use anyhow::Context;
use clap::error::ErrorKind;
use clap::{CommandFactory, Parser};
use fairway::verdict::{MALFUNCTION, Verdict};
const INVALID_INVOCATION: &str =
"The invocation is invalid. Correct the command line against the usage on stderr and retry.";
fn main() -> ExitCode {
match std::panic::catch_unwind(run_to_completion) {
Ok(Ok(code)) => code,
Ok(Err(e)) => malfunction(&format!("{e:#}")),
Err(_) => malfunction("panicked; details above"),
}
}
fn run_to_completion() -> anyhow::Result<ExitCode> {
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.context("could not start the async runtime")?;
runtime.block_on(run_or_interrupted())
}
async fn run_or_interrupted() -> anyhow::Result<ExitCode> {
tokio::select! {
result = run() => result,
caught = signal::termination() => {
let name = caught.context("could not install the signal handlers")?;
anyhow::bail!("terminated by {name}")
}
}
}
fn malfunction(cause: &str) -> ExitCode {
let mut stdout = std::io::stdout();
let _ = writeln!(
stdout,
"fairway failed: stop and report the error output to the user."
)
.and_then(|()| stdout.flush());
let _ = writeln!(std::io::stderr(), "fairway: {cause}");
ExitCode::from(MALFUNCTION)
}
async fn run() -> anyhow::Result<ExitCode> {
match cli::Cli::try_parse() {
Ok(cli::Cli {}) => {
let mut stdout = std::io::stdout();
write!(stdout, "{}", cli::Cli::command().render_help())?;
stdout.flush()?;
Ok(ExitCode::SUCCESS)
}
Err(e) if matches!(e.kind(), ErrorKind::DisplayHelp | ErrorKind::DisplayVersion) => {
let mut stdout = std::io::stdout();
write!(stdout, "{e}")?;
stdout.flush()?;
Ok(ExitCode::SUCCESS)
}
Err(e) => {
let _ = write!(std::io::stderr(), "{e}");
Ok(Verdict::Adjust(INVALID_INVOCATION.to_owned()).render())
}
}
}