mirror of
https://github.com/kdheepak/taskwarrior-tui.git
synced 2025-08-24 23:46:41 +02:00
feat: Use color_eyre instead of anyhow ✨
This commit is contained in:
parent
fe171ea35a
commit
2bb15f7cf2
18 changed files with 1180 additions and 412 deletions
16
src/app.rs
16
src/app.rs
|
@ -5,13 +5,13 @@ use std::{
|
|||
convert::TryInto,
|
||||
fs, io,
|
||||
io::{Read, Write},
|
||||
path::Path,
|
||||
path::{Path, PathBuf},
|
||||
sync::{mpsc, Arc, Mutex},
|
||||
time::{Duration, Instant, SystemTime},
|
||||
};
|
||||
|
||||
use anyhow::{anyhow, Context as AnyhowContext, Result};
|
||||
use chrono::{DateTime, Datelike, FixedOffset, Local, NaiveDate, NaiveDateTime, TimeZone, Timelike};
|
||||
use color_eyre::eyre::{anyhow, Context as AnyhowContext, Result};
|
||||
use crossterm::{
|
||||
event::{DisableMouseCapture, EnableMouseCapture},
|
||||
execute,
|
||||
|
@ -57,7 +57,8 @@ use crate::{
|
|||
scrollbar::Scrollbar,
|
||||
table::{Row, Table, TableMode, TableState},
|
||||
task_report::TaskReportTable,
|
||||
ui, utils,
|
||||
ui,
|
||||
utils::{self, get_data_dir},
|
||||
};
|
||||
|
||||
const MAX_LINE: usize = 4096;
|
||||
|
@ -177,7 +178,6 @@ pub struct TaskwarriorTui {
|
|||
pub all_tasks: Vec<Task>,
|
||||
pub task_details: HashMap<Uuid, String>,
|
||||
pub marked: HashSet<Uuid>,
|
||||
// stores index of current task that is highlighted
|
||||
pub current_selection: usize,
|
||||
pub current_selection_uuid: Option<Uuid>,
|
||||
pub current_selection_id: Option<u64>,
|
||||
|
@ -240,7 +240,7 @@ impl TaskwarriorTui {
|
|||
.output()
|
||||
.context("Unable to run `task --version`")?;
|
||||
|
||||
let task_version = Versioning::new(String::from_utf8_lossy(&output.stdout).trim()).context("Unable to get version string")?;
|
||||
let task_version = Versioning::new(String::from_utf8_lossy(&output.stdout).trim()).ok_or(anyhow!("Unable to get version string"))?;
|
||||
|
||||
let (w, h) = crossterm::terminal::size().unwrap_or((50, 15));
|
||||
|
||||
|
@ -251,6 +251,8 @@ impl TaskwarriorTui {
|
|||
};
|
||||
let event_loop = crate::event::EventLoop::new(tick_rate, init_event_loop);
|
||||
|
||||
let data_dir = get_data_dir();
|
||||
|
||||
let mut app = Self {
|
||||
should_quit: false,
|
||||
dirty: true,
|
||||
|
@ -280,8 +282,8 @@ impl TaskwarriorTui {
|
|||
keyconfig: kc,
|
||||
terminal_width: w,
|
||||
terminal_height: h,
|
||||
filter_history: HistoryContext::new("filter.history"),
|
||||
command_history: HistoryContext::new("command.history"),
|
||||
filter_history: HistoryContext::new("filter.history", data_dir.clone()),
|
||||
command_history: HistoryContext::new("command.history", data_dir.clone()),
|
||||
history_status: None,
|
||||
completion_list: CompletionList::with_items(vec![]),
|
||||
show_completion_pane: false,
|
||||
|
|
|
@ -21,7 +21,7 @@ pub fn generate_cli_app() -> clap::Command {
|
|||
.short('c')
|
||||
.long("config")
|
||||
.value_name("FOLDER")
|
||||
.help("Sets the config folder for taskwarrior-tui (currently not used)")
|
||||
.help("Sets the config folder for taskwarrior-tui")
|
||||
.action(clap::ArgAction::Set),
|
||||
)
|
||||
.arg(
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use std::{collections::HashMap, error::Error, str};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use color_eyre::eyre::{eyre, Context, Result};
|
||||
use ratatui::{
|
||||
style::{Color, Modifier, Style},
|
||||
symbols::{bar::FULL, line::DOUBLE_VERTICAL},
|
||||
|
@ -465,14 +465,14 @@ impl Config {
|
|||
|
||||
fn get_rule_precedence_color(data: &str) -> Vec<String> {
|
||||
let data = Self::get_config("rule.precedence.color", data)
|
||||
.context("Unable to parse `task show rule.precedence.color`.")
|
||||
.ok_or_else(|| eyre!("Unable to parse `task show rule.precedence.color`."))
|
||||
.unwrap();
|
||||
data.split(',').map(ToString::to_string).collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
fn get_uda_priority_values(data: &str) -> Vec<String> {
|
||||
let data = Self::get_config("uda.priority.values", data)
|
||||
.context("Unable to parse `task show uda.priority.values`.")
|
||||
.ok_or_else(|| eyre!("Unable to parse `task show uda.priority.values`."))
|
||||
.unwrap();
|
||||
data.split(',').map(ToString::to_string).collect::<Vec<_>>()
|
||||
}
|
||||
|
@ -489,7 +489,7 @@ impl Config {
|
|||
|
||||
fn get_data_location(data: &str) -> String {
|
||||
Self::get_config("data.location", data)
|
||||
.context("Unable to parse `task show data.location`.")
|
||||
.ok_or_else(|| eyre!("Unable to parse `task show data.location`."))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ use std::{
|
|||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use color_eyre::eyre::{anyhow, Result};
|
||||
use rustyline::{
|
||||
error::ReadlineError,
|
||||
history::{DefaultHistory, History, SearchDirection},
|
||||
|
@ -16,17 +16,9 @@ pub struct HistoryContext {
|
|||
}
|
||||
|
||||
impl HistoryContext {
|
||||
pub fn new(filename: &str) -> Self {
|
||||
pub fn new(filename: &str, data_path: PathBuf) -> Self {
|
||||
let history = DefaultHistory::new();
|
||||
|
||||
let data_path = if let Ok(s) = std::env::var("TASKWARRIOR_TUI_DATA") {
|
||||
PathBuf::from(s)
|
||||
} else {
|
||||
dirs::data_local_dir()
|
||||
.map(|d| d.join("taskwarrior-tui"))
|
||||
.expect("Unable to create configuration directory for taskwarrior-tui")
|
||||
};
|
||||
|
||||
std::fs::create_dir_all(&data_path).unwrap_or_else(|_| panic!("Unable to create configuration directory in {:?}", &data_path));
|
||||
|
||||
let data_path = data_path.join(filename);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use std::{collections::HashSet, error::Error, hash::Hash};
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use color_eyre::eyre::{anyhow, Result};
|
||||
use log::{error, info, warn};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
|
166
src/main.rs
166
src/main.rs
|
@ -17,6 +17,7 @@ mod pane;
|
|||
mod scrollbar;
|
||||
mod table;
|
||||
mod task_report;
|
||||
mod tui;
|
||||
mod ui;
|
||||
mod utils;
|
||||
|
||||
|
@ -29,8 +30,8 @@ use std::{
|
|||
time::Duration,
|
||||
};
|
||||
|
||||
use anyhow::Result;
|
||||
use app::{Mode, TaskwarriorTui};
|
||||
use color_eyre::eyre::Result;
|
||||
use crossterm::{
|
||||
cursor,
|
||||
event::{DisableMouseCapture, EnableMouseCapture, EventStream},
|
||||
|
@ -39,91 +40,20 @@ use crossterm::{
|
|||
};
|
||||
use futures::stream::{FuturesUnordered, StreamExt};
|
||||
use log::{debug, error, info, log_enabled, trace, warn, Level, LevelFilter};
|
||||
use log4rs::{
|
||||
append::file::FileAppender,
|
||||
config::{Appender, Config, Logger, Root},
|
||||
encode::pattern::PatternEncoder,
|
||||
};
|
||||
use path_clean::PathClean;
|
||||
use ratatui::{backend::CrosstermBackend, Terminal};
|
||||
use utils::{get_config_dir, get_data_dir};
|
||||
|
||||
use crate::{action::Action, event::Event, keyconfig::KeyConfig};
|
||||
use crate::{
|
||||
action::Action,
|
||||
event::Event,
|
||||
keyconfig::KeyConfig,
|
||||
utils::{initialize_logging, initialize_panic_handler},
|
||||
};
|
||||
|
||||
const LOG_PATTERN: &str = "{d(%Y-%m-%d %H:%M:%S)} | {l} | {f}:{L} | {m}{n}";
|
||||
|
||||
pub fn destruct_terminal() {
|
||||
disable_raw_mode().unwrap();
|
||||
execute!(io::stdout(), LeaveAlternateScreen, DisableMouseCapture).unwrap();
|
||||
execute!(io::stdout(), cursor::Show).unwrap();
|
||||
}
|
||||
|
||||
pub fn initialize_logging() {
|
||||
let data_local_dir = if let Ok(s) = std::env::var("TASKWARRIOR_TUI_DATA") {
|
||||
PathBuf::from(s)
|
||||
} else {
|
||||
dirs::data_local_dir()
|
||||
.expect("Unable to find data directory for taskwarrior-tui")
|
||||
.join("taskwarrior-tui")
|
||||
};
|
||||
|
||||
std::fs::create_dir_all(&data_local_dir).unwrap_or_else(|_| panic!("Unable to create {:?}", data_local_dir));
|
||||
|
||||
let logfile = FileAppender::builder()
|
||||
.encoder(Box::new(PatternEncoder::new(LOG_PATTERN)))
|
||||
.append(false)
|
||||
.build(data_local_dir.join("taskwarrior-tui.log"))
|
||||
.expect("Failed to build log file appender.");
|
||||
|
||||
let levelfilter = match std::env::var("TASKWARRIOR_TUI_LOG_LEVEL").unwrap_or_else(|_| "info".to_string()).as_str() {
|
||||
"off" => LevelFilter::Off,
|
||||
"warn" => LevelFilter::Warn,
|
||||
"info" => LevelFilter::Info,
|
||||
"debug" => LevelFilter::Debug,
|
||||
"trace" => LevelFilter::Trace,
|
||||
_ => LevelFilter::Info,
|
||||
};
|
||||
let config = Config::builder()
|
||||
.appender(Appender::builder().build("logfile", Box::new(logfile)))
|
||||
.logger(Logger::builder().build("taskwarrior_tui", levelfilter))
|
||||
.build(Root::builder().appender("logfile").build(LevelFilter::Info))
|
||||
.expect("Failed to build logging config.");
|
||||
|
||||
log4rs::init_config(config).expect("Failed to initialize logging.");
|
||||
}
|
||||
|
||||
pub fn absolute_path(path: impl AsRef<Path>) -> io::Result<PathBuf> {
|
||||
let path = path.as_ref();
|
||||
|
||||
let absolute_path = if path.is_absolute() {
|
||||
path.to_path_buf()
|
||||
} else {
|
||||
env::current_dir()?.join(path)
|
||||
}
|
||||
.clean();
|
||||
|
||||
Ok(absolute_path)
|
||||
}
|
||||
|
||||
async fn tui_main(report: &str) -> Result<()> {
|
||||
panic::set_hook(Box::new(|panic_info| {
|
||||
destruct_terminal();
|
||||
better_panic::Settings::auto().create_panic_handler()(panic_info);
|
||||
}));
|
||||
|
||||
let mut app = app::TaskwarriorTui::new(report, true).await?;
|
||||
|
||||
let mut terminal = app.start_tui()?;
|
||||
|
||||
let r = app.run(&mut terminal).await;
|
||||
|
||||
app.pause_tui().await?;
|
||||
|
||||
r
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
better_panic::install();
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
let matches = cli::generate_cli_app().get_matches();
|
||||
|
||||
let config = matches.get_one::<String>("config");
|
||||
|
@ -133,58 +63,42 @@ fn main() -> Result<()> {
|
|||
let binding = String::from("next");
|
||||
let report = matches.get_one::<String>("report").unwrap_or(&binding);
|
||||
|
||||
if let Some(e) = config {
|
||||
if env::var("TASKWARRIOR_TUI_CONFIG").is_err() {
|
||||
// if environment variable is not set, this env::var returns an error
|
||||
env::set_var(
|
||||
"TASKWARRIOR_TUI_CONFIG",
|
||||
absolute_path(PathBuf::from(e)).expect("Unable to get path for config"),
|
||||
)
|
||||
} else {
|
||||
warn!("TASKWARRIOR_TUI_CONFIG environment variable cannot be set.")
|
||||
}
|
||||
}
|
||||
let config_dir = config.map(PathBuf::from).unwrap_or_else(get_config_dir);
|
||||
let data_dir = data.map(PathBuf::from).unwrap_or_else(get_data_dir);
|
||||
|
||||
if let Some(e) = data {
|
||||
if env::var("TASKWARRIOR_TUI_DATA").is_err() {
|
||||
// if environment variable is not set, this env::var returns an error
|
||||
env::set_var(
|
||||
"TASKWARRIOR_TUI_DATA",
|
||||
absolute_path(PathBuf::from(e)).expect("Unable to get path for data"),
|
||||
)
|
||||
} else {
|
||||
warn!("TASKWARRIOR_TUI_DATA environment variable cannot be set.")
|
||||
}
|
||||
}
|
||||
// if let Some(e) = taskrc {
|
||||
// if env::var("TASKRC").is_err() {
|
||||
// // if environment variable is not set, this env::var returns an error
|
||||
// env::set_var("TASKRC", absolute_path(PathBuf::from(e)).expect("Unable to get path for taskrc"))
|
||||
// } else {
|
||||
// warn!("TASKRC environment variable cannot be set.")
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if let Some(e) = taskdata {
|
||||
// if env::var("TASKDATA").is_err() {
|
||||
// // if environment variable is not set, this env::var returns an error
|
||||
// env::set_var("TASKDATA", absolute_path(PathBuf::from(e)).expect("Unable to get path for taskdata"))
|
||||
// } else {
|
||||
// warn!("TASKDATA environment variable cannot be set.")
|
||||
// }
|
||||
// }
|
||||
|
||||
if let Some(e) = taskrc {
|
||||
if env::var("TASKRC").is_err() {
|
||||
// if environment variable is not set, this env::var returns an error
|
||||
env::set_var("TASKRC", absolute_path(PathBuf::from(e)).expect("Unable to get path for taskrc"))
|
||||
} else {
|
||||
warn!("TASKRC environment variable cannot be set.")
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(e) = taskdata {
|
||||
if env::var("TASKDATA").is_err() {
|
||||
// if environment variable is not set, this env::var returns an error
|
||||
env::set_var("TASKDATA", absolute_path(PathBuf::from(e)).expect("Unable to get path for taskdata"))
|
||||
} else {
|
||||
warn!("TASKDATA environment variable cannot be set.")
|
||||
}
|
||||
}
|
||||
|
||||
initialize_logging();
|
||||
initialize_logging(data_dir)?;
|
||||
initialize_panic_handler()?;
|
||||
|
||||
debug!("getting matches from clap...");
|
||||
debug!("report = {:?}", &report);
|
||||
debug!("config = {:?}", &config);
|
||||
|
||||
let r = tokio::runtime::Builder::new_multi_thread()
|
||||
.enable_all()
|
||||
.build()?
|
||||
.block_on(async { tui_main(report).await });
|
||||
let mut app = app::TaskwarriorTui::new(report, true).await?;
|
||||
|
||||
let mut terminal = app.start_tui()?;
|
||||
|
||||
let r = app.run(&mut terminal).await;
|
||||
|
||||
app.pause_tui().await?;
|
||||
|
||||
if let Err(err) = r {
|
||||
eprintln!("\x1b[0;31m[taskwarrior-tui error]\x1b[0m: {}\n\nIf you need additional help, please report as a github issue on https://github.com/kdheepak/taskwarrior-tui", err);
|
||||
std::process::exit(1);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use std::fmt;
|
||||
|
||||
use anyhow::{anyhow, Context as AnyhowContext, Result};
|
||||
use color_eyre::eyre::{anyhow, Context as AnyhowContext, Result};
|
||||
|
||||
const NAME: &str = "Name";
|
||||
const TYPE: &str = "Remaining";
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use std::ops::Index;
|
||||
|
||||
use anyhow::Result;
|
||||
use color_eyre::eyre::Result;
|
||||
|
||||
use crate::{
|
||||
action::Action,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use std::fmt;
|
||||
|
||||
use anyhow::{anyhow, Context as AnyhowContext, Result};
|
||||
use color_eyre::eyre::{anyhow, Context as AnyhowContext, Result};
|
||||
|
||||
const COL_WIDTH: usize = 21;
|
||||
const PROJECT_HEADER: &str = "Name";
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use std::{error::Error, process::Command};
|
||||
|
||||
use anyhow::Result;
|
||||
use chrono::{DateTime, Datelike, Local, NaiveDate, NaiveDateTime, TimeZone};
|
||||
use color_eyre::eyre::Result;
|
||||
use itertools::join;
|
||||
use task_hookrs::{task::Task, uda::UDAValue};
|
||||
use unicode_truncate::UnicodeTruncateStr;
|
||||
|
@ -14,7 +14,7 @@ pub fn format_date_time(dt: NaiveDateTime) -> String {
|
|||
|
||||
pub fn format_date(dt: NaiveDateTime) -> String {
|
||||
let offset = Local.offset_from_utc_datetime(&dt);
|
||||
let dt = DateTime::<Local>::from_utc(dt, offset);
|
||||
let dt = DateTime::<Local>::from_naive_utc_and_offset(dt, offset);
|
||||
dt.format("%Y-%m-%d").to_string()
|
||||
}
|
||||
|
||||
|
|
150
src/tui.rs
Normal file
150
src/tui.rs
Normal file
|
@ -0,0 +1,150 @@
|
|||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use color_eyre::eyre::Result;
|
||||
use crossterm::{
|
||||
cursor,
|
||||
event::{Event as CrosstermEvent, KeyEvent, KeyEventKind, MouseEvent},
|
||||
terminal::{EnterAlternateScreen, LeaveAlternateScreen},
|
||||
};
|
||||
use futures::{FutureExt, StreamExt};
|
||||
use ratatui::backend::CrosstermBackend as Backend;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use tokio::{
|
||||
sync::mpsc::{self, UnboundedReceiver, UnboundedSender},
|
||||
task::JoinHandle,
|
||||
};
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
pub type CrosstermFrame<'a> = ratatui::Frame<'a, Backend<std::io::Stderr>>;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
|
||||
pub enum Event {
|
||||
Quit,
|
||||
Error,
|
||||
Closed,
|
||||
Tick,
|
||||
Key(KeyEvent),
|
||||
Mouse(MouseEvent),
|
||||
Resize(u16, u16),
|
||||
}
|
||||
|
||||
pub struct Tui {
|
||||
pub terminal: ratatui::Terminal<Backend<std::io::Stderr>>,
|
||||
pub task: JoinHandle<()>,
|
||||
pub cancellation_token: CancellationToken,
|
||||
pub event_rx: UnboundedReceiver<Event>,
|
||||
pub event_tx: UnboundedSender<Event>,
|
||||
pub tick_rate: usize,
|
||||
}
|
||||
|
||||
impl Tui {
|
||||
pub fn new(tick_rate: usize) -> Result<Self> {
|
||||
let terminal = ratatui::Terminal::new(Backend::new(std::io::stderr()))?;
|
||||
let (event_tx, event_rx) = mpsc::unbounded_channel();
|
||||
let cancellation_token = CancellationToken::new();
|
||||
let task = tokio::spawn(async {});
|
||||
Ok(Self {
|
||||
terminal,
|
||||
task,
|
||||
cancellation_token,
|
||||
event_rx,
|
||||
event_tx,
|
||||
tick_rate,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn start(&mut self) {
|
||||
let tick_rate = std::time::Duration::from_millis(self.tick_rate as u64);
|
||||
self.cancellation_token.cancel();
|
||||
self.cancellation_token = CancellationToken::new();
|
||||
let _cancellation_token = self.cancellation_token.clone();
|
||||
let _event_tx = self.event_tx.clone();
|
||||
self.task = tokio::spawn(async move {
|
||||
let mut reader = crossterm::event::EventStream::new();
|
||||
let mut interval = tokio::time::interval(tick_rate);
|
||||
loop {
|
||||
let delay = interval.tick();
|
||||
let crossterm_event = reader.next().fuse();
|
||||
tokio::select! {
|
||||
_ = _cancellation_token.cancelled() => {
|
||||
break;
|
||||
}
|
||||
maybe_event = crossterm_event => {
|
||||
match maybe_event {
|
||||
Some(Ok(evt)) => {
|
||||
match evt {
|
||||
CrosstermEvent::Key(key) => {
|
||||
if key.kind == KeyEventKind::Press {
|
||||
_event_tx.send(Event::Key(key)).unwrap();
|
||||
}
|
||||
},
|
||||
CrosstermEvent::Resize(x, y) => {
|
||||
_event_tx.send(Event::Resize(x, y)).unwrap();
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
Some(Err(_)) => {
|
||||
_event_tx.send(Event::Error).unwrap();
|
||||
}
|
||||
None => {},
|
||||
}
|
||||
},
|
||||
_ = delay => {
|
||||
_event_tx.send(Event::Tick).unwrap();
|
||||
},
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn enter(&mut self) -> Result<()> {
|
||||
crossterm::terminal::enable_raw_mode()?;
|
||||
crossterm::execute!(std::io::stderr(), EnterAlternateScreen, cursor::Hide)?;
|
||||
self.start();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn exit(&self) -> Result<()> {
|
||||
crossterm::execute!(std::io::stderr(), LeaveAlternateScreen, cursor::Show)?;
|
||||
crossterm::terminal::disable_raw_mode()?;
|
||||
self.cancellation_token.cancel();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn suspend(&self) -> Result<()> {
|
||||
self.exit()?;
|
||||
#[cfg(not(windows))]
|
||||
signal_hook::low_level::raise(signal_hook::consts::signal::SIGTSTP)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn resume(&mut self) -> Result<()> {
|
||||
self.enter()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn next(&mut self) -> Option<Event> {
|
||||
self.event_rx.recv().await
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Tui {
|
||||
type Target = ratatui::Terminal<Backend<std::io::Stderr>>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.terminal
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for Tui {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.terminal
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Tui {
|
||||
fn drop(&mut self) {
|
||||
self.exit().unwrap();
|
||||
}
|
||||
}
|
148
src/utils.rs
148
src/utils.rs
|
@ -15,3 +15,151 @@ impl ChangeListener for Changeset {
|
|||
|
||||
fn replace(&mut self, idx: usize, old: &str, new: &str) {}
|
||||
}
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use color_eyre::eyre::{anyhow, Context, Result};
|
||||
use directories::ProjectDirs;
|
||||
use lazy_static::lazy_static;
|
||||
use tracing::error;
|
||||
use tracing_error::ErrorLayer;
|
||||
use tracing_subscriber::{self, filter::EnvFilter, prelude::__tracing_subscriber_SubscriberExt, util::SubscriberInitExt, Layer};
|
||||
|
||||
use crate::tui::Tui;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref CRATE_NAME: String = env!("CARGO_CRATE_NAME").to_uppercase().to_string();
|
||||
pub static ref DATA_FOLDER: Option<PathBuf> = std::env::var(format!("{}_DATA", CRATE_NAME.clone())).ok().map(PathBuf::from);
|
||||
pub static ref CONFIG_FOLDER: Option<PathBuf> = std::env::var(format!("{}_CONFIG", CRATE_NAME.clone())).ok().map(PathBuf::from);
|
||||
pub static ref GIT_COMMIT_HASH: String = std::env::var(format!("{}_GIT_INFO", CRATE_NAME.clone())).unwrap_or_else(|_| String::from("Unknown"));
|
||||
pub static ref LOG_FILE: String = format!("{}.log", CRATE_NAME.to_lowercase());
|
||||
}
|
||||
|
||||
fn project_directory() -> Option<ProjectDirs> {
|
||||
ProjectDirs::from("com", "kdheepak", CRATE_NAME.clone().to_lowercase().as_str())
|
||||
}
|
||||
|
||||
pub fn initialize_panic_handler() -> Result<()> {
|
||||
let (panic_hook, eyre_hook) = color_eyre::config::HookBuilder::default().into_hooks();
|
||||
eyre_hook.install()?;
|
||||
std::panic::set_hook(Box::new(move |panic_info| {
|
||||
if let Ok(t) = Tui::new(0) {
|
||||
if let Err(r) = t.exit() {
|
||||
error!("Unable to exit Terminal: {:?}", r);
|
||||
}
|
||||
}
|
||||
let msg = format!("{}", panic_hook.panic_report(panic_info));
|
||||
tracing::error!("{}", strip_ansi_escapes::strip_str(&msg));
|
||||
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!("{}", msg);
|
||||
std::process::exit(libc::EXIT_FAILURE);
|
||||
}));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_data_dir() -> PathBuf {
|
||||
let directory = if let Some(s) = DATA_FOLDER.clone() {
|
||||
s
|
||||
} else if let Some(proj_dirs) = project_directory() {
|
||||
proj_dirs.data_local_dir().to_path_buf()
|
||||
} else {
|
||||
PathBuf::from(".").join(".data")
|
||||
};
|
||||
directory
|
||||
}
|
||||
|
||||
pub fn get_config_dir() -> PathBuf {
|
||||
let directory = if let Some(s) = CONFIG_FOLDER.clone() {
|
||||
s
|
||||
} else if let Some(proj_dirs) = project_directory() {
|
||||
proj_dirs.config_local_dir().to_path_buf()
|
||||
} else {
|
||||
PathBuf::from(".").join(".config")
|
||||
};
|
||||
directory
|
||||
}
|
||||
|
||||
pub fn initialize_logging(directory: PathBuf) -> Result<()> {
|
||||
std::fs::create_dir_all(directory.clone())?;
|
||||
let log_path = directory.join(LOG_FILE.clone());
|
||||
let log_file = std::fs::File::create(log_path)?;
|
||||
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(EnvFilter::from_default_env());
|
||||
|
||||
tracing_subscriber::registry()
|
||||
.with(file_subscriber)
|
||||
.with(tui_logger::tracing_subscriber_layer())
|
||||
.with(ErrorLayer::default())
|
||||
.init();
|
||||
let default_level = std::env::var("RUST_LOG").map_or(log::LevelFilter::Info, |val| match val.to_lowercase().as_str() {
|
||||
"off" => log::LevelFilter::Off,
|
||||
"error" => log::LevelFilter::Error,
|
||||
"warn" => log::LevelFilter::Warn,
|
||||
"info" => log::LevelFilter::Info,
|
||||
"debug" => log::LevelFilter::Debug,
|
||||
"trace" => log::LevelFilter::Trace,
|
||||
_ => log::LevelFilter::Info,
|
||||
});
|
||||
tui_logger::set_default_level(default_level);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Similar to the `std::dbg!` macro, but generates `tracing` events rather
|
||||
/// than printing to stdout.
|
||||
///
|
||||
/// By default, the verbosity level for the generated events is `DEBUG`, but
|
||||
/// this can be customized.
|
||||
#[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 commit_hash = GIT_COMMIT_HASH.clone();
|
||||
|
||||
// let current_exe_path = PathBuf::from(clap::crate_name!()).display().to_string();
|
||||
let config_dir_path = get_config_dir().display().to_string();
|
||||
let data_dir_path = get_data_dir().display().to_string();
|
||||
|
||||
format!(
|
||||
"\
|
||||
{commit_hash}
|
||||
|
||||
Authors: {author}
|
||||
|
||||
Config directory: {config_dir_path}
|
||||
Data directory: {data_dir_path}"
|
||||
)
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue