#![deny(unsafe_code)]
#![deny(static_mut_refs)]
#![deny(clippy::declare_interior_mutable_const)]
#![deny(clippy::borrow_interior_mutable_const)]
#![deny(clippy::redundant_clone)]
#![deny(clippy::ptr_arg)]
#![deny(clippy::needless_borrow)]
#![deny(clippy::cloned_instead_of_copied)]
use std::io::{self, IsTerminal, Write};
use std::process::ExitCode;
use atomwrite::constants::{EXIT_BROKEN_PIPE, EXIT_FAILURE, EXIT_SUCCESS, EXIT_TIMEOUT};
use clap::FromArgMatches;
#[global_allocator]
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
fn main() -> ExitCode {
real_main()
}
fn real_main() -> ExitCode {
let args: Vec<String> = std::env::args_os()
.map(|a| a.to_string_lossy().into_owned())
.collect();
atomwrite::platform::init_console();
let _early_shutdown = atomwrite::signal::install_handlers_early();
human_panic::setup_panic!();
match answer_schema_request(&args) {
Some(exit) => exit,
None => match atomwrite::cli::Cli::parser()
.try_get_matches()
.and_then(|m| atomwrite::cli::Cli::from_arg_matches(&m))
{
Ok(cli) => execute(&cli),
Err(e) => atomwrite::runtime::handle_clap_parse_error(e),
},
}
}
fn answer_schema_request(args: &[String]) -> Option<ExitCode> {
if let Some(schema_cmd) = atomwrite::runtime::prescan_json_schema() {
let mut out = io::stdout().lock();
match atomwrite::emit_schema_by_name(&schema_cmd, &mut out) {
Ok(true) => return Some(ExitCode::from(EXIT_SUCCESS as u8)),
Ok(false) => {}
Err(e) => return Some(schema_write_failure(e)),
}
}
match atomwrite::runtime::prescan_print_schema(args) {
Ok(exit) => exit,
Err(e) => Some(schema_write_failure(e)),
}
}
fn schema_write_failure(error: anyhow::Error) -> ExitCode {
ExitCode::from(atomwrite::best_effort::settle_error_report(
Err(error),
EXIT_FAILURE as u8,
))
}
fn execute(cli: &atomwrite::cli::Cli) -> ExitCode {
atomwrite::runtime::init_locale(cli.global.lang.as_deref());
let workspace = cli
.global
.resolve_workspace()
.unwrap_or_else(|_| std::path::PathBuf::from("."));
let _guard = atomwrite::runtime::init_tracing(
cli.global.verbose,
cli.global.quiet,
cli.global.color_mode(),
&workspace,
cli.global.config.as_deref(),
);
atomwrite::runtime::install_panic_hook();
atomwrite::locale::log_resolved_locale();
if let Some(ref ready) = cli.global.ready_file {
atomwrite::signal::write_ready_file(ready);
}
let shutdown = atomwrite::signal::install_handlers()
.inspect_err(|e| tracing::warn!(%e, "signal handler registration failed"))
.ok();
let stdout = io::stdout();
let stdin_is_tty = io::stdin().is_terminal();
let exit = match atomwrite::run(cli, io::stdin(), stdout.lock(), stdin_is_tty) {
Ok(()) => match &shutdown {
Some(sig) if sig.is_shutdown() => {
if sig.is_timeout() {
atomwrite::signal::write_timeout_message();
} else {
atomwrite::signal::write_shutdown_message();
}
tracing::info!(exit = sig.exit_code(), "shutdown initiated");
ExitCode::from(sig.exit_code())
}
_ => ExitCode::from(EXIT_SUCCESS as u8),
},
Err(err) => map_run_error(&err, cli),
};
tracing::info!("shutdown complete");
exit
}
fn map_run_error(err: &anyhow::Error, cli: &atomwrite::cli::Cli) -> ExitCode {
if err
.downcast_ref::<atomwrite::output::VerdictPublished>()
.is_some()
{
let (_, exit) = atomwrite::output::failure_identity(err);
return ExitCode::from(exit);
}
if let Some(aw_err) = err.downcast_ref::<atomwrite::error::AtomwriteError>() {
if matches!(aw_err, atomwrite::error::AtomwriteError::BrokenPipe) {
return ExitCode::from(EXIT_BROKEN_PIPE as u8);
}
if let atomwrite::error::AtomwriteError::Cancelled { exit, .. } = aw_err
&& atomwrite::signal::is_global_shutdown()
{
if *exit == EXIT_TIMEOUT as u8 {
atomwrite::signal::write_timeout_message();
} else {
atomwrite::signal::write_shutdown_message();
}
tracing::info!(exit = *exit, "shutdown via Cancelled");
return ExitCode::from(*exit);
}
return publish_error_envelope(aw_err, cli);
}
if let Some(io_err) = atomwrite::runtime::find_io_error(err) {
if io_err == io::ErrorKind::BrokenPipe {
return ExitCode::from(EXIT_BROKEN_PIPE as u8);
}
let aw_err = atomwrite::runtime::io_to_atomwrite_error(io_err, err);
return publish_error_envelope(&aw_err, cli);
}
let untyped = atomwrite::error::AtomwriteError::InternalError {
reason: format!("{err:#}"),
};
publish_error_envelope(&untyped, cli)
}
fn is_byte_stream_mode(cli: &atomwrite::cli::Cli) -> bool {
match &cli.command {
atomwrite::cli::Commands::Search(args) => match args.format {
Some(format) => format == atomwrite::cli::SearchFormat::Text,
None => atomwrite::config::tuning().search_format_is_text,
},
atomwrite::cli::Commands::Read(args) | atomwrite::cli::Commands::Stat(args) => {
matches!(args.format, atomwrite::cli::OutputFormat::Raw)
}
_ => false,
}
}
fn publish_text_mode_error(aw_err: &atomwrite::error::AtomwriteError) -> ExitCode {
if !matches!(aw_err, atomwrite::error::AtomwriteError::NoMatches) {
atomwrite::best_effort::best_effort(
writeln!(io::stderr().lock(), "atomwrite: {aw_err}"),
"report text-mode error on stderr",
);
}
ExitCode::from(aw_err.exit_code())
}
fn publish_error_envelope(
aw_err: &atomwrite::error::AtomwriteError,
cli: &atomwrite::cli::Cli,
) -> ExitCode {
if is_byte_stream_mode(cli) {
return publish_text_mode_error(aw_err);
}
let mut out = io::stdout().lock();
let ctx = atomwrite::error::ErrorContext {
workspace_provided: cli.global.workspace.is_some(),
workspace: cli.global.workspace.clone(),
verb: match cli.command {
atomwrite::cli::Commands::Apply(_) => Some("apply"),
atomwrite::cli::Commands::Edit(_) => Some("edit"),
_ => None,
},
};
let shape =
atomwrite::output::OutputShape::from_global_args(&cli.global).unwrap_or_else(|_| {
atomwrite::output::OutputShape {
verbose: cli.global.verbose > 0,
redact: cli.global.redact,
..atomwrite::output::OutputShape::default()
}
});
let target = atomwrite::error_envelope_target(cli);
let written = atomwrite::output::write_error_json_shaped_with_target(
&mut out,
aw_err,
None,
&ctx,
&shape,
target.as_ref().map(|(r, s)| (r, s.as_str())),
)
.and_then(|()| out.flush().map_err(anyhow::Error::from));
ExitCode::from(atomwrite::best_effort::settle_error_report(
written,
aw_err.exit_code(),
))
}