use std::path::PathBuf;
use anyhow::{Context, Result};
use color_eyre::eyre::Result as EyreResult;
use etcetera::{choose_app_strategy, AppStrategy, AppStrategyArgs};
use lazy_static::lazy_static;
use tracing::error;
use tracing_error::ErrorLayer;
use tracing_subscriber::{self, prelude::__tracing_subscriber_SubscriberExt, util::SubscriberInitExt, Layer};
const VERSION_MESSAGE: &str =
concat!(env!("CARGO_PKG_VERSION"), "-", env!("VERGEN_GIT_DESCRIBE"), " (", env!("VERGEN_BUILD_DATE"), ")");
lazy_static! {
pub static ref PROJECT_NAME: String = env!("CARGO_CRATE_NAME").to_uppercase().to_string();
pub static ref DATA_FOLDER: Option<PathBuf> =
std::env::var(format!("{}_DATA", PROJECT_NAME.clone())).ok().map(PathBuf::from);
pub static ref CONFIG_FOLDER: Option<PathBuf> =
std::env::var(format!("{}_CONFIG", PROJECT_NAME.clone())).ok().map(PathBuf::from);
pub static ref LOG_ENV: String = format!("{}_LOGLEVEL", PROJECT_NAME.clone());
pub static ref LOG_FILE: String = format!("{}.log", env!("CARGO_PKG_NAME"));
}
const APP_QUALIFIER: &str = "com";
const APP_ORG: &str = "yassinebridi";
const APP_NAME: &str = "serpl";
pub fn strategy() -> Result<impl AppStrategy> {
choose_app_strategy(AppStrategyArgs {
top_level_domain: APP_QUALIFIER.to_owned(),
author: APP_ORG.to_owned(),
app_name: APP_NAME.to_owned(),
})
.context("Failed to create app strategy")
}
pub fn config_directory() -> Result<PathBuf> {
Ok(strategy()?.config_dir())
}
pub fn data_directory() -> Result<PathBuf> {
Ok(strategy()?.data_dir())
}
pub fn state() -> Result<PathBuf> {
let strategy = strategy()?;
match strategy.state_dir() {
Some(path) => Ok(path),
None => Ok(strategy.data_dir()),
}
}
pub fn initialize_panic_handler() -> EyreResult<()> {
let (panic_hook, eyre_hook) = color_eyre::config::HookBuilder::default()
.panic_section(format!("This is a bug. Consider reporting it at {}", env!("CARGO_PKG_REPOSITORY")))
.capture_span_trace_by_default(false)
.display_location_section(false)
.display_env_section(false)
.into_hooks();
eyre_hook.install()?;
std::panic::set_hook(Box::new(move |panic_info| {
if let Ok(mut t) = crate::tui::Tui::new() {
if let Err(r) = t.exit() {
error!("Unable to exit Terminal: {:?}", r);
}
}
#[cfg(not(debug_assertions))]
{
use human_panic::{handle_dump, print_msg, Metadata};
let meta = Metadata {
version: env!("CARGO_PKG_VERSION").into(),
name: env!("CARGO_PKG_NAME").into(),
authors: env!("CARGO_PKG_AUTHORS").replace(':', ", ").into(),
homepage: env!("CARGO_PKG_HOMEPAGE").into(),
};
let file_path = handle_dump(&meta, panic_info);
print_msg(file_path, &meta).expect("human-panic: printing error message to console failed");
eprintln!("{}", panic_hook.panic_report(panic_info)); }
let msg = format!("{}", panic_hook.panic_report(panic_info));
log::error!("Error: {}", strip_ansi_escapes::strip_str(msg));
#[cfg(debug_assertions)]
{
better_panic::Settings::auto()
.most_recent_first(false)
.lineno_suffix(true)
.verbosity(better_panic::Verbosity::Full)
.create_panic_handler()(panic_info);
}
std::process::exit(libc::EXIT_FAILURE);
}));
Ok(())
}
pub fn get_data_dir() -> PathBuf {
if let Some(s) = DATA_FOLDER.clone() {
s
} else if let Ok(data_dir) = data_directory() {
data_dir
} else {
PathBuf::from(".").join(".data")
}
}
pub fn get_config_dir() -> PathBuf {
if let Some(s) = CONFIG_FOLDER.clone() {
s
} else if let Ok(config_dir) = config_directory() {
config_dir
} else {
PathBuf::from(".").join(".config")
}
}
pub fn initialize_logging() -> Result<()> {
let directory = get_data_dir();
std::fs::create_dir_all(directory.clone())?;
let log_path = directory.join(LOG_FILE.clone());
let log_file = std::fs::File::create(log_path)?;
std::env::set_var(
"RUST_LOG",
std::env::var("RUST_LOG")
.or_else(|_| std::env::var(LOG_ENV.clone()))
.unwrap_or_else(|_| format!("{}=info", env!("CARGO_CRATE_NAME"))),
);
let file_subscriber = tracing_subscriber::fmt::layer()
.with_file(true)
.with_line_number(true)
.with_writer(log_file)
.with_target(false)
.with_ansi(false)
.with_filter(tracing_subscriber::filter::EnvFilter::from_default_env());
tracing_subscriber::registry().with(file_subscriber).with(ErrorLayer::default()).init();
Ok(())
}
#[macro_export]
macro_rules! trace_dbg {
(target: $target:expr, level: $level:expr, $ex:expr) => {{
match $ex {
value => {
tracing::event!(target: $target, $level, ?value, stringify!($ex));
value
}
}
}};
(level: $level:expr, $ex:expr) => {
trace_dbg!(target: module_path!(), level: $level, $ex)
};
(target: $target:expr, $ex:expr) => {
trace_dbg!(target: $target, level: tracing::Level::DEBUG, $ex)
};
($ex:expr) => {
trace_dbg!(level: tracing::Level::DEBUG, $ex)
};
}
pub fn version() -> String {
let author = clap::crate_authors!();
let config_dir_path = get_config_dir().display().to_string();
let data_dir_path = get_data_dir().display().to_string();
format!(
"\
{VERSION_MESSAGE}
Authors: {author}
Config directory: {config_dir_path}"
)
}
pub fn is_git_repo(path: PathBuf) -> bool {
let git_dir = path.join(".git");
git_dir.exists()
}
use ratatui::layout::{Constraint, Direction, Layout, Rect};
#[allow(dead_code)]
pub(crate) fn centered_rect(percent_x: u16, percent_y: u16, r: Rect) -> Rect {
let popup_layout = Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Percentage((100 - percent_y) / 2),
Constraint::Percentage(percent_y),
Constraint::Percentage((100 - percent_y) / 2),
])
.split(r);
Layout::default()
.direction(Direction::Horizontal)
.constraints([
Constraint::Percentage((100 - percent_x) / 2),
Constraint::Percentage(percent_x),
Constraint::Percentage((100 - percent_x) / 2),
])
.split(popup_layout[1])[1]
}
#[allow(dead_code)]
pub(crate) fn centered_rect_with_size(width: u16, height: u16, r: Rect) -> Rect {
let width = width.min(r.width);
let height = height.min(r.height);
let remaining_width = r.width.saturating_sub(width);
let remaining_height = r.height.saturating_sub(height);
let popup_layout = Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Max(remaining_height / 2),
Constraint::Length(height),
Constraint::Max(remaining_height / 2),
])
.split(r);
Layout::default()
.direction(Direction::Horizontal)
.constraints([
Constraint::Max(remaining_width / 2),
Constraint::Length(width),
Constraint::Max(remaining_width / 2),
])
.split(popup_layout[1])[1]
}