miv_editor/
utils.rs

1use std::path::PathBuf;
2
3use color_eyre::eyre::Result;
4use directories::ProjectDirs;
5use lazy_static::lazy_static;
6use tracing::error;
7use tracing_error::ErrorLayer;
8use tracing_subscriber::{
9    self, prelude::__tracing_subscriber_SubscriberExt, util::SubscriberInitExt, Layer,
10};
11
12lazy_static! {
13    pub static ref PROJECT_NAME: String = env!("CARGO_CRATE_NAME").to_uppercase().to_string();
14    pub static ref DATA_FOLDER: Option<PathBuf> =
15        std::env::var(format!("{}_DATA", PROJECT_NAME.clone()))
16            .ok()
17            .map(PathBuf::from);
18    pub static ref CONFIG_FOLDER: Option<PathBuf> =
19        std::env::var(format!("{}_CONFIG", PROJECT_NAME.clone()))
20            .ok()
21            .map(PathBuf::from);
22    pub static ref LOG_ENV: String = format!("{}_LOGLEVEL", PROJECT_NAME.clone());
23    pub static ref LOG_FILE: String = format!("{}.log", env!("CARGO_PKG_NAME"));
24}
25
26fn project_directory() -> Option<ProjectDirs> {
27    ProjectDirs::from("", "", &format!("{}-{}", env!("CARGO_PKG_NAME"), "editor"))
28}
29
30pub fn initialize_panic_handler() -> Result<()> {
31    let (panic_hook, eyre_hook) = color_eyre::config::HookBuilder::default()
32        .panic_section(format!(
33            "This is a bug. Consider reporting it at {}",
34            env!("CARGO_PKG_REPOSITORY")
35        ))
36        .capture_span_trace_by_default(false)
37        .display_location_section(false)
38        .display_env_section(false)
39        .into_hooks();
40    eyre_hook.install()?;
41    std::panic::set_hook(Box::new(move |panic_info| {
42        if let Ok(mut t) = crate::tui::Tui::new() {
43            if let Err(r) = t.exit() {
44                error!("Unable to exit Terminal: {:?}", r);
45            }
46        }
47
48        #[cfg(not(debug_assertions))]
49        {
50            use human_panic::{handle_dump, print_msg, Metadata};
51            let meta = Metadata {
52                version: env!("CARGO_PKG_VERSION").into(),
53                name: env!("CARGO_PKG_NAME").into(),
54                authors: env!("CARGO_PKG_AUTHORS").replace(':', ", ").into(),
55                homepage: env!("CARGO_PKG_HOMEPAGE").into(),
56            };
57
58            let file_path = handle_dump(&meta, panic_info);
59            // prints human-panic message
60            print_msg(file_path, &meta)
61                .expect("human-panic: printing error message to console failed");
62            eprintln!("{}", panic_hook.panic_report(panic_info)); // prints color-eyre stack trace to stderr
63        }
64        let msg = format!("{}", panic_hook.panic_report(panic_info));
65        log::error!("Error: {}", strip_ansi_escapes::strip_str(msg));
66
67        #[cfg(debug_assertions)]
68        {
69            // Better Panic stacktrace that is only enabled when debugging.
70            better_panic::Settings::auto()
71                .most_recent_first(false)
72                .lineno_suffix(true)
73                .verbosity(better_panic::Verbosity::Full)
74                .create_panic_handler()(panic_info);
75        }
76
77        std::process::exit(libc::EXIT_FAILURE);
78    }));
79    Ok(())
80}
81
82pub fn get_data_dir() -> PathBuf {
83    let directory = if let Some(s) = DATA_FOLDER.clone() {
84        s
85    } else if let Some(proj_dirs) = project_directory() {
86        proj_dirs.data_local_dir().to_path_buf()
87    } else {
88        PathBuf::from(".").join(".data")
89    };
90    directory
91}
92
93pub fn get_config_dir() -> PathBuf {
94    let directory = if let Some(s) = CONFIG_FOLDER.clone() {
95        s
96    } else if let Some(proj_dirs) = project_directory() {
97        proj_dirs.config_local_dir().to_path_buf()
98    } else {
99        PathBuf::from(".").join(".config")
100    };
101    directory
102}
103
104pub fn initialize_logging() -> Result<()> {
105    let directory = get_data_dir();
106    std::fs::create_dir_all(directory.clone())?;
107    let log_path = directory.join(LOG_FILE.clone());
108    let log_file = std::fs::File::create(log_path)?;
109    std::env::set_var(
110        "RUST_LOG",
111        std::env::var("RUST_LOG")
112            .or_else(|_| std::env::var(LOG_ENV.clone()))
113            .unwrap_or_else(|_| format!("{}=info", env!("CARGO_CRATE_NAME"))),
114    );
115    let file_subscriber = tracing_subscriber::fmt::layer()
116        .with_file(true)
117        .with_line_number(true)
118        .with_writer(log_file)
119        .with_target(false)
120        .with_ansi(false)
121        .with_filter(tracing_subscriber::filter::EnvFilter::from_default_env());
122    tracing_subscriber::registry()
123        .with(file_subscriber)
124        .with(ErrorLayer::default())
125        .init();
126    Ok(())
127}
128
129/// Similar to the `std::dbg!` macro, but generates `tracing` events rather
130/// than printing to stdout.
131///
132/// By default, the verbosity level for the generated events is `DEBUG`, but
133/// this can be customized.
134#[macro_export]
135macro_rules! trace_dbg {
136    (target: $target:expr, level: $level:expr, $ex:expr) => {{
137        match $ex {
138            value => {
139                tracing::event!(target: $target, $level, ?value, stringify!($ex));
140                value
141            }
142        }
143    }};
144    (level: $level:expr, $ex:expr) => {
145        trace_dbg!(target: module_path!(), level: $level, $ex)
146    };
147    (target: $target:expr, $ex:expr) => {
148        trace_dbg!(target: $target, level: tracing::Level::DEBUG, $ex)
149    };
150    ($ex:expr) => {
151        trace_dbg!(level: tracing::Level::DEBUG, $ex)
152    };
153}
154
155pub fn version() -> String {
156    let author = clap::crate_authors!();
157
158    // let current_exe_path = PathBuf::from(clap::crate_name!()).display().to_string();
159    let config_dir_path = get_config_dir().display().to_string();
160    let data_dir_path = get_data_dir().display().to_string();
161
162    format!(
163        "\
164Authors: {author}
165
166Config directory: {config_dir_path}
167Data directory: {data_dir_path}"
168    )
169}