feat(multibinary): support running the binary without arguments (using a sensible user interface as the default), and improve error reporting by using snafu

This commit is contained in:
J / Jacob Babich
2024-10-16 02:07:57 -04:00
parent aee2678638
commit 7480cdcf96

View File

@@ -1,6 +1,12 @@
use std::{fs::create_dir_all, io::ErrorKind, path::PathBuf}; use std::{
fs::create_dir_all,
io::ErrorKind,
path::{Path, PathBuf},
};
use cfg_if::cfg_if;
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use snafu::{ResultExt, Snafu};
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
struct Args { struct Args {
@@ -11,24 +17,91 @@ struct Args {
)] )]
application_data_directory: PathBuf, application_data_directory: PathBuf,
// If there is a GUI or TUI available from this binary,
// then calling this program without arguments
// is acceptable: it will launch a user interface
#[cfg(any(feature = "gui-eframe", feature = "tui-ratatui"))]
#[command(subcommand)]
command: Option<Command>,
#[cfg(not(any(feature = "gui-eframe", feature = "tui-ratatui")))]
#[command(subcommand)] #[command(subcommand)]
command: Command, command: Command,
} }
#[derive(Debug, Subcommand)] #[derive(Debug, Subcommand)]
enum Command { enum Command {
#[cfg(feature = "cli-clap")]
#[command(alias = "cli")]
CliClap(ac_qu_ai_nt_cli_clap::Args),
#[cfg(feature = "gui-eframe")] #[cfg(feature = "gui-eframe")]
#[command(alias = "gui")] #[command(alias = "gui")]
GuiEframe, GuiEframe,
#[cfg(feature = "tui-ratatui")] #[cfg(feature = "tui-ratatui")]
#[command(alias = "tui")] #[command(alias = "tui")]
TuiRatatui, TuiRatatui,
#[cfg(feature = "cli-clap")]
#[command(alias = "cli")]
CliClap(ac_qu_ai_nt_cli_clap::Args),
} }
fn main() { // When this program is run without arguments,
// it will launch a user interface
cfg_if!(
// with the GUI (made with eframe)
// being considered more appealing (made the default)
if #[cfg(feature = "gui-eframe")] {
impl Default for Command {
fn default() -> Self {
Command::GuiEframe
}
}
}
// than the TUI (made with Ratatui)
else if #[cfg(feature = "tui-ratatui")] {
impl Default for Command {
fn default() -> Self {
Command::TuiRatatui
}
}
}
// with it not being logical to specify
// the CLI (made with clap)
// as an option,
// because if `ac-qu-ai-nt ask "why is the sky blue?"`
// were accepted and worked when `cli-clap` was the only
// interface enabled, then when another interface
// like `gui-eframe` were enabled, it would stop working
// (only able to work as `ac-qu-ai-nt cli-clap ask "why is the sky blue?"`)
// so it should be required to do it the way that would work
// in both cases from the beginning
);
#[derive(Debug, Snafu)]
enum ApplicationError {
#[snafu(display("failed to create the {path:?} directory"))]
DirectoryCreationError {
path: PathBuf,
source: std::io::Error,
},
}
/// Create the directory and its parents,
/// but don't return an error if it already exists and is a directory
fn create_dir_all_exist_ok(path: impl AsRef<Path>) -> Result<(), std::io::Error> {
match create_dir_all(&path) {
Ok(()) => Ok(()),
Err(e) => {
if e.kind() == ErrorKind::AlreadyExists && path.as_ref().is_dir() {
Ok(())
} else {
Err(e)
}
}
}
}
#[snafu::report]
fn main() -> Result<(), ApplicationError> {
let Args { let Args {
application_data_directory, application_data_directory,
command, command,
@@ -37,23 +110,19 @@ fn main() {
#[cfg(feature = "tracing")] #[cfg(feature = "tracing")]
tracing_subscriber::fmt::init(); tracing_subscriber::fmt::init();
match create_dir_all(&application_data_directory) { create_dir_all_exist_ok(&application_data_directory).with_context(|_| {
Ok(()) => {} DirectoryCreationSnafu {
Err(e) if e.kind() == ErrorKind::AlreadyExists => {} path: application_data_directory.clone(),
Err(e) => {
panic!("{}", e);
} }
} })?;
let tracing_directory = application_data_directory.join("logs"); let tracing_directory = application_data_directory.join("logs");
create_dir_all_exist_ok(&tracing_directory).with_context(|_| DirectoryCreationSnafu {
path: tracing_directory.clone(),
})?;
match create_dir_all(&tracing_directory) { #[cfg(any(feature = "gui-eframe", feature = "tui-ratatui"))]
Ok(()) => {} let command = command.unwrap_or_default();
Err(e) if e.kind() == ErrorKind::AlreadyExists => {}
Err(e) => {
panic!("{}", e);
}
}
match command { match command {
#[cfg(feature = "cli-clap")] #[cfg(feature = "cli-clap")]
@@ -63,4 +132,6 @@ fn main() {
#[cfg(feature = "tui-ratatui")] #[cfg(feature = "tui-ratatui")]
Command::TuiRatatui => ac_qu_ai_nt_tui_ratatui::main(), Command::TuiRatatui => ac_qu_ai_nt_tui_ratatui::main(),
} }
Ok(())
} }