use std::convert::TryInto;
use std::env;
use anyhow::{Context, Result};
use clap::ArgMatches;
use tokio::runtime::Runtime;
use tracing::info;
use mhost::app::console::{Console, ConsoleOpts};
use mhost::app::logging::Logging;
use mhost::app::modules::{self, PartialError};
use mhost::app::AppConfig;
use mhost::app::{cli_parser, ExitStatus};
use mhost::nameserver::predefined;
fn main() {
let res = do_main();
let exit_status = match res {
Ok(exit_status) => exit_status,
Err(err) => match err.downcast_ref::<PartialError>() {
Some(PartialError::Failed(exit_status)) => exit_status.to_owned(),
_ => {
let console = Console::new(ConsoleOpts::default());
console.error(format!("Error: {:#}", err));
ExitStatus::UnrecoverableError
}
},
};
std::process::exit(exit_status as i32);
}
fn exit_subcommand_invalid() -> ExitStatus {
ExitStatus::Ok
}
fn do_main() -> Result<ExitStatus> {
let args = cli_parser::create_parser().get_matches();
let color = !args.get_flag("no-color");
let debug = args.get_flag("debug");
setup_terminal(&args, color);
setup_logging(&args, color, debug);
info!("Set up logging.");
let app_config = match parse_global_args(&args) {
Ok(app_config) => app_config,
Err(err) => {
let console = Console::new(ConsoleOpts::default());
console.error(format!("Invalid configuration: {:#}", err));
return Ok(ExitStatus::ConfigParsingFailed);
}
};
info!("Parsed global args.");
let console = setup_console(&app_config);
let runtime = setup_tokio(app_config.max_worker_threads())?;
info!(
"Started Async Runtime with {} worker threads.",
app_config
.max_worker_threads()
.map(|x| x.to_string())
.unwrap_or_else(|| "Tokio default number of".to_string())
);
info!("Running command");
let res = runtime.block_on(async { run_command(&args, &app_config, &console).await });
info!("Finished command.");
res
}
fn setup_tokio(num_threads: Option<usize>) -> Result<Runtime> {
let mut builder = tokio::runtime::Builder::new_multi_thread();
builder.enable_all();
if let Some(num_threads) = num_threads {
builder.worker_threads(num_threads);
}
builder.build().context("Failed to build Async Runtime")
}
fn setup_terminal(args: &ArgMatches, color: bool) {
if !color {
mhost::app::common::styles::no_color_mode();
}
if args.get_flag("ascii") {
mhost::app::common::styles::ascii_mode();
}
}
fn setup_logging(args: &ArgMatches, color: bool, debug: bool) {
Logging::new(args.get_count("v"), env::var_os("RUST_LOG"), color, debug)
.start()
.expect("failed to initialize logging");
}
fn parse_global_args(args: &ArgMatches) -> Result<AppConfig> {
let app_config: AppConfig = args.try_into()?;
Ok(app_config)
}
fn setup_console(app_config: &AppConfig) -> Console {
let console_opts = ConsoleOpts::from(app_config);
Console::new(console_opts)
}
async fn run_command(args: &ArgMatches, app_config: &AppConfig, console: &Console) -> Result<ExitStatus> {
if app_config.list_predefined {
list_predefined_nameservers(console);
return Ok(ExitStatus::Ok);
}
match args.subcommand_name() {
Some("check") => modules::check::run(args, app_config).await,
Some("diff") => modules::diff::run(args, app_config).await,
Some("discover") => modules::discover::run(args, app_config).await,
Some("dnssec") => modules::dnssec::run(args, app_config).await,
Some("domain-lookup") => modules::domain_lookup::run(args, app_config).await,
Some("completions") => modules::completions::run(args),
Some("info") => modules::info::run(args),
Some("server-lists") => modules::get_server_lists::run(args, app_config).await,
Some("lookup") => modules::lookup::run(args, app_config).await,
Some("propagation") => modules::propagation::run(args, app_config).await,
Some("trace") => modules::trace::run(args, app_config).await,
Some("verify") => modules::verify::run(args, app_config).await,
_ => {
cli_parser::show_commands();
Ok(exit_subcommand_invalid())
}
}
}
pub fn list_predefined_nameservers(console: &Console) {
console.caption("Predefined servers:");
for ns in predefined::nameserver_configs() {
console.itemize(format!("{}", ns));
}
}